Merge pull request #6081 from oqtane/dev

10.1.0 Release
This commit is contained in:
Shaun Walker
2026-02-25 14:23:35 -05:00
committed by GitHub
150 changed files with 4772 additions and 1098 deletions

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Configurations>Debug;Release</Configurations>
<Version>10.0.4</Version>
<Version>10.1.0</Version>
<Product>Oqtane</Product>
<Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company>
@@ -10,7 +10,7 @@
<Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.4</PackageReleaseNotes>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.1.0</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
</PropertyGroup>

View File

@@ -12,10 +12,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="10.0.2" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="10.0.2" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.3" />
</ItemGroup>
<ItemGroup>
@@ -23,7 +23,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Oqtane.Client" Version="10.0.4" />
<PackageReference Include="Oqtane.Client" Version="10.1.0" />
</ItemGroup>
</Project>

View File

@@ -9,6 +9,7 @@
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.Extensions.Localization
@using Microsoft.JSInterop
@using Microsoft.AspNetCore.Authorization
@using Oqtane
@using Oqtane.Models

View File

@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>Oqtane.Application.Template</id>
<version>10.0.4</version>
<version>10.1.0</version>
<title>Oqtane Application Template For Blazor</title>
<authors>Shaun Walker</authors>
<requireLicenseAcceptance>false</requireLicenseAcceptance>

View File

@@ -22,9 +22,9 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.3" />
</ItemGroup>
<ItemGroup>
@@ -33,7 +33,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Oqtane.Server" Version="10.0.4" />
<PackageReference Include="Oqtane.Server" Version="10.1.0" />
</ItemGroup>
</Project>

View File

@@ -4,7 +4,7 @@
@namespace [Owner].Module.[Module]
@inherits ModuleBase
@inject I[Module]Service [Module]Service
@inject I[Module]Service Client[Module]Service
@inject NavigationManager NavigationManager
@inject IStringLocalizer<Edit> Localizer
@@ -50,7 +50,7 @@
if (PageState.Action == "Edit")
{
_id = Int32.Parse(PageState.QueryString["id"]);
[Module] [Module] = await [Module]Service.Get[Module]Async(_id, ModuleState.ModuleId);
[Module] [Module] = await Client[Module]Service.Get[Module]Async(_id, ModuleState.ModuleId);
if ([Module] != null)
{
_name = [Module].Name;
@@ -81,14 +81,14 @@
[Module] [Module] = new [Module]();
[Module].ModuleId = ModuleState.ModuleId;
[Module].Name = _name;
[Module] = await [Module]Service.Add[Module]Async([Module]);
[Module] = await Client[Module]Service.Add[Module]Async([Module]);
await logger.LogInformation("[Module] Added {[Module]}", [Module]);
}
else
{
[Module] [Module] = await [Module]Service.Get[Module]Async(_id, ModuleState.ModuleId);
[Module] [Module] = await Client[Module]Service.Get[Module]Async(_id, ModuleState.ModuleId);
[Module].Name = _name;
await [Module]Service.Update[Module]Async([Module]);
await Client[Module]Service.Update[Module]Async([Module]);
await logger.LogInformation("[Module] Updated {[Module]}", [Module]);
}
NavigationManager.NavigateTo(NavigateUrl());

View File

@@ -3,7 +3,7 @@
@namespace [Owner].Module.[Module]
@inherits ModuleBase
@inject I[Module]Service [Module]Service
@inject I[Module]Service Client[Module]Service
@inject NavigationManager NavigationManager
@inject IStringLocalizer<Index> Localizer
@@ -52,7 +52,7 @@ else
{
try
{
_[Module]s = await [Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
_[Module]s = await Client[Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
}
catch (Exception ex)
{
@@ -65,9 +65,9 @@ else
{
try
{
await [Module]Service.Delete[Module]Async([Module].[Module]Id, ModuleState.ModuleId);
await Client[Module]Service.Delete[Module]Async([Module].[Module]Id, ModuleState.ModuleId);
await logger.LogInformation("[Module] Deleted {[Module]}", [Module]);
_[Module]s = await [Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
_[Module]s = await Client[Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
StateHasChanged();
}
catch (Exception ex)

View File

@@ -7,22 +7,10 @@ using Oqtane.Shared;
namespace [Owner].Module.[Module].Services
{
public interface I[Module]Service
public class Client[Module]Service : ServiceBase, I[Module]Service
{
Task<List<Models.[Module]>> Get[Module]sAsync(int ModuleId);
Task<Models.[Module]> Get[Module]Async(int [Module]Id, int ModuleId);
Task<Models.[Module]> Add[Module]Async(Models.[Module] [Module]);
Task<Models.[Module]> Update[Module]Async(Models.[Module] [Module]);
Task Delete[Module]Async(int [Module]Id, int ModuleId);
}
public class [Module]Service : ServiceBase, I[Module]Service
{
public [Module]Service(HttpClient http, SiteState siteState) : base(http, siteState) { }
public Client[Module]Service(HttpClient http, SiteState siteState) : base(http, siteState) { }
private string Apiurl => CreateApiUrl("[Module]");

View File

@@ -11,7 +11,7 @@ namespace [Owner].Module.[Module].Startup
{
if (!services.Any(s => s.ServiceType == typeof(I[Module]Service)))
{
services.AddScoped<I[Module]Service, [Module]Service>();
services.AddScoped<I[Module]Service, Client[Module]Service>();
}
}
}

View File

@@ -0,0 +1,18 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace [Owner].Module.[Module].Services
{
public interface I[Module]Service
{
Task<List<Models.[Module]>> Get[Module]sAsync(int ModuleId);
Task<Models.[Module]> Get[Module]Async(int [Module]Id, int ModuleId);
Task<Models.[Module]> Add[Module]Async(Models.[Module] [Module]);
Task<Models.[Module]> Update[Module]Async(Models.[Module] [Module]);
Task Delete[Module]Async(int [Module]Id, int ModuleId);
}
}

View File

@@ -11,7 +11,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Oqtane.Shared" Version="10.0.4" />
<PackageReference Include="Oqtane.Shared" Version="10.1.0" />
</ItemGroup>
</Project>

View File

@@ -57,6 +57,9 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddScoped<ICookieConsentService, CookieConsentService>();
services.AddScoped<ITimeZoneService, TimeZoneService>();
services.AddScoped<IMigrationHistoryService, MigrationHistoryService>();
services.AddScoped<ISiteGroupService, SiteGroupService>();
services.AddScoped<ISiteGroupMemberService, SiteGroupMemberService>();
services.AddScoped<ISiteTaskService, SiteTaskService>();
services.AddScoped<IOutputCacheService, OutputCacheService>();
// providers

View File

@@ -0,0 +1,117 @@
@namespace Oqtane.Modules.Admin.GlobalReplace
@using System.Text.Json
@inherits ModuleBase
@inject ISiteTaskService SiteTaskService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="find" HelpText="Specify the content which needs to be replaced" ResourceKey="Find">Find What: </Label>
<div class="col-sm-9">
<input id="find" class="form-control" @bind="@_find" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="replace" HelpText="Specify the replacement content" ResourceKey="Replace">Replace With: </Label>
<div class="col-sm-9">
<input id="replace" class="form-control" @bind="@_replace" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="casesensitive" HelpText="Specify if the replacement operation should be case sensitive" ResourceKey="CaseSensitive">Match Case? </Label>
<div class="col-sm-9">
<select id="casesensitive" class="form-select" @bind="@_caseSensitive">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="site" HelpText="Specify if site properties should be updated (ie. name, headcontent, bodycontent)" ResourceKey="Site">Site Properties? </Label>
<div class="col-sm-9">
<select id="site" class="form-select" @bind="@_site">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="pages" HelpText="Specify if page properties should be updated (ie. name, title, headcontent, bodycontent)" ResourceKey="Pages">Page Properties? </Label>
<div class="col-sm-9">
<select id="pages" class="form-select" @bind="@_pages">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="modules" HelpText="Specify if module properties should be updated (ie. title, header, footer)" ResourceKey="Modules">Module Properties? </Label>
<div class="col-sm-9">
<select id="modules" class="form-select" @bind="@_modules">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="content" HelpText="Specify if module content should be updated" ResourceKey="Content">Module Content? </Label>
<div class="col-sm-9">
<select id="content" class="form-select" @bind="@_content">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
<br /><br />
<ActionDialog Header="Global Replace" Message="This Operation is Permanent. Are You Sure You Wish To Proceed?" Action="Replace" Class="btn btn-primary" OnClick="@(async () => await Save())" ResourceKey="GlobalReplace" />
<br /><br />
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override string Title => "Global Replace";
private string _find;
private string _replace;
private string _caseSensitive = "True";
private string _site = "True";
private string _pages = "True";
private string _modules = "True";
private string _content = "True";
private async Task Save()
{
try
{
if (!string.IsNullOrEmpty(_find) && !string.IsNullOrEmpty(_replace))
{
var replace = new GlobalReplace
{
Find = _find,
Replace = _replace,
CaseSensitive = bool.Parse(_caseSensitive),
Site = bool.Parse(_site),
Pages = bool.Parse(_pages),
Modules = bool.Parse(_modules),
Content = bool.Parse(_content)
};
var siteTask = new SiteTask(PageState.Site.SiteId, "Global Replace", "Oqtane.Infrastructure.GlobalReplaceTask, Oqtane.Server", JsonSerializer.Serialize(replace));
await SiteTaskService.AddSiteTaskAsync(siteTask);
AddModuleMessage(Localizer["Success.Save"], MessageType.Success);
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Global Replace Settings {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Save"], MessageType.Error);
}
}
}

View File

@@ -46,7 +46,8 @@
</div>
</div>
</form>
<Section Name="Information" ResourceKey="Information">
<br />
<Section Name="Information" ResourceKey="Information">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="moduledefinitionname" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
@@ -97,7 +98,13 @@
}
</div>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="fingerprint" HelpText="A unique identifier for the module's static resources. This value can be changed by clicking the Save option below (ie. cache busting)." ResourceKey="Fingerprint">Fingerprint: </Label>
<div class="col-sm-9">
<input id="fingerprint" class="form-control" @bind="@_fingerprint" disabled />
</div>
</div>
</div>
</Section>
<br />
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@SharedLocalizer["Save"]</button>
@@ -231,6 +238,7 @@
private string _url = "";
private string _contact = "";
private string _license = "";
private string _fingerprint = "";
private List<Permission> _permissions = null;
private string _createdby;
private DateTime _createdon;
@@ -266,6 +274,7 @@
_url = moduleDefinition.Url;
_contact = moduleDefinition.Contact;
_license = moduleDefinition.License;
_fingerprint = moduleDefinition.Fingerprint;
_permissions = moduleDefinition.PermissionList;
_createdby = moduleDefinition.CreatedBy;
_createdon = moduleDefinition.CreatedOn;

View File

@@ -73,27 +73,29 @@
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="page" HelpText="The page that the module is located on" ResourceKey="Page">Page: </Label>
<Label Class="col-sm-3" For="page" HelpText="The page that the module is located on. Please note that shared modules cannot be moved to other pages." ResourceKey="Page">Page: </Label>
<div class="col-sm-9">
<select id="page" class="form-select" @bind="@_pageId" required>
@if (PageState.Page.UserId != null)
{
@if (PageState.Page.UserId != null || _isShared)
{
<select id="page" class="form-select" @bind="@_pageId" required disabled>
<option value="@PageState.Page.PageId">@(PageState.Page.Name)</option>
}
else
</select>
}
else
{
<select id="page" class="form-select" @bind="@_pageId" required>
@if (_pages != null)
{
if (_pages != null)
foreach (Page p in _pages)
{
foreach (Page p in _pages)
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, p.PermissionList))
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, p.PermissionList))
{
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
}
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
}
}
}
</select>
</select>
}
</div>
</div>
</div>
@@ -161,6 +163,7 @@
private string _pane;
private string _containerType;
private string _allPages = "false";
private bool _isShared = false;
private string _header = "";
private string _footer = "";
private string _permissionNames = "";
@@ -207,6 +210,7 @@
_expirydate = Utilities.UtcAsLocalDate(pagemodule.ExpiryDate);
_allPages = pagemodule.Module.AllPages.ToString();
_isShared = pagemodule.Module.IsShared;
createdby = pagemodule.Module.CreatedBy;
createdon = pagemodule.Module.CreatedOn;
modifiedby = pagemodule.Module.ModifiedBy;
@@ -276,7 +280,6 @@
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
if (!string.IsNullOrEmpty(_title))
{
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
@@ -284,7 +287,32 @@
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
return;
}
// update module settings first
if (_moduleSettingsType != null)
{
if (_moduleSettings is ISettingsControl moduleSettingsControl)
{
// module settings updated using explicit interface
await moduleSettingsControl.UpdateSettings();
}
else
{
// legacy support - module settings updated by convention ( ie. by calling a public method named "UpdateSettings" in settings component )
_moduleSettings?.GetType().GetMethod("UpdateSettings")?.Invoke(_moduleSettings, null);
}
}
// update container settings
if (_containerSettingsType != null && _containerSettings is ISettingsControl containerSettingsControl)
{
await containerSettingsControl.UpdateSettings();
}
// update page module
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
var pageId = pagemodule.PageId; // preserve
var pane = pagemodule.Pane; // preserve
pagemodule.PageId = int.Parse(_pageId);
pagemodule.Title = _title;
pagemodule.Pane = _pane;
@@ -302,33 +330,21 @@
pagemodule.Header = _header;
pagemodule.Footer = _footer;
await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
// update page module order if page or pane changed
if (pageId != pagemodule.PageId || pane != pagemodule.Pane)
{
await PageModuleService.UpdatePageModuleOrderAsync(pageId, pane); // old page/pane
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); // new page/pane
}
// update module
var module = await ModuleService.GetModuleAsync(ModuleState.ModuleId);
module.AllPages = bool.Parse(_allPages);
module.PageModuleId = ModuleState.PageModuleId;
module.PermissionList = _permissionGrid.GetPermissionList();
await ModuleService.UpdateModuleAsync(module);
if (_moduleSettingsType != null)
{
if (_moduleSettings is ISettingsControl moduleSettingsControl)
{
// module settings updated using explicit interface
await moduleSettingsControl.UpdateSettings();
}
else
{
// legacy support - module settings updated by convention ( ie. by calling a public method named "UpdateSettings" in settings component )
_moduleSettings?.GetType().GetMethod("UpdateSettings")?.Invoke(_moduleSettings, null);
}
}
if (_containerSettingsType != null && _containerSettings is ISettingsControl containerSettingsControl)
{
await containerSettingsControl.UpdateSettings();
}
NavigationManager.NavigateTo(PageState.ReturnUrl);
}
else

View File

@@ -46,7 +46,7 @@
<Label Class="col-sm-3" For="move" HelpText="Select the location where you would like the page to be moved in relation to other pages" ResourceKey="Move">Move: </Label>
<div class="col-sm-9">
<select id="move" class="form-select" @bind="@_insert" required>
@if (_parentid == _currentparentid)
@if (_parentid == _currentparentid && !_copy)
{
<option value="=">&lt;@Localizer["ThisLocation.Keep"]&gt;</option>
}
@@ -225,7 +225,10 @@
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
<br />
<br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
@if (!_copy)
{
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
}
</TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions" Heading="Permissions">
<div class="container">
@@ -247,30 +250,34 @@
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</div>
</TabPanel>
<TabPanel Name="PageModules" Heading="Modules" ResourceKey="PageModules">
<Pager Items="_pageModules">
<Header>
@if (!_copy)
{
<TabPanel Name="PageModules" Heading="Modules" ResourceKey="PageModules">
<Pager Items="_pageModules">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["ModuleTitle"]</th>
<th>@Localizer["ModuleDefinition"]</th>
</Header>
<Row>
<td><ActionLink Action="Settings" Text="Edit" Path="@_actualpath" ModuleId="@context.ModuleId" Security="SecurityAccessLevel.Edit" PermissionList="@context.PermissionList" ResourceKey="ModuleSettings" /></td>
<td><ActionDialog Header="Delete Module" Message="Are You Sure You Wish To Delete This Module?" Action="Delete" Security="SecurityAccessLevel.Edit" PermissionList="@context.PermissionList" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /></td>
<td>@context.Title</td>
<td>@context.ModuleDefinition?.Name</td>
</Row>
</Pager>
</TabPanel>
@if (_themeSettingsType != null)
{
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
@_themeSettingsComponent
<br />
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</Header>
<Row>
<td><ActionLink Action="Settings" Text="Edit" Path="@_actualpath" ModuleId="@context.ModuleId" Security="SecurityAccessLevel.Edit" PermissionList="@context.PermissionList" ResourceKey="ModuleSettings" /></td>
<td><ActionDialog Header="Delete Module" Message="Are You Sure You Wish To Delete This Module?" Action="Delete" Security="SecurityAccessLevel.Edit" PermissionList="@context.PermissionList" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /></td>
<td>@context.Title</td>
<td>@context.ModuleDefinition?.Name</td>
</Row>
</Pager>
</TabPanel>
@if (_themeSettingsType != null)
{
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
@_themeSettingsComponent
<br />
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</TabPanel>
}
}
</TabStrip>
}
@@ -349,6 +356,7 @@
private List<ThemeControl> _containers = new List<ThemeControl>();
private List<Page> _pages;
private int _pageId;
private bool _copy = false;
private string _name;
private string _currentparentid;
private string _parentid = "-1";
@@ -394,6 +402,10 @@
{
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
_pageId = Int32.Parse(PageState.QueryString["id"]);
if (PageState.QueryString.ContainsKey("copy"))
{
_copy = bool.Parse(PageState.QueryString["copy"]);
}
_page = await PageService.GetPageAsync(_pageId);
_icons = await SystemService.GetIconsAsync();
_iconresources = Utilities.GetFullTypeName(typeof(IconResources).AssemblyQualifiedName);
@@ -413,7 +425,7 @@
_children = new List<Page>();
foreach (Page p in _pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid, CultureInfo.InvariantCulture))))
{
if (p.PageId != _pageId && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
if ((p.PageId != _pageId || _copy) && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
{
_children.Add(p);
}
@@ -440,6 +452,12 @@
_expirydate = Utilities.UtcAsLocalDate(_page.ExpiryDate);
_ispersonalizable = _page.IsPersonalizable.ToString();
if (_copy)
{
_insert = ">";
_childid = _page.PageId;
}
// appearance
_title = _page.Title;
_themetype = _page.ThemeType;
@@ -470,6 +488,19 @@
// permissions
_permissions = _page.PermissionList;
_updatemodulepermissions = "True";
if (_copy)
{
_permissions = _page.PermissionList.Select(item => new Permission
{
SiteId = item.SiteId,
EntityName = item.EntityName,
EntityId = -1,
PermissionName = item.PermissionName,
RoleName = item.RoleName,
UserId = item.UserId,
IsAuthorized = item.IsAuthorized,
}).ToList();
}
// page modules
var modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
@@ -484,6 +515,13 @@
_deletedon = _page.DeletedOn;
ThemeSettings();
if (_copy)
{
_name = "";
_path = "";
}
_initialized = true;
}
else
@@ -576,10 +614,18 @@
await ScrollToPageTop();
return;
}
if (!string.IsNullOrEmpty(_themetype) && _containertype != "-")
{
string currentPath = _page.Path;
if (_copy)
{
_page = new Page();
_page.SiteId = PageState.Site.SiteId;
currentPath = "";
}
_page.Name = _name;
if (_parentid == "-1")
@@ -635,6 +681,13 @@
return;
}
// update theme settings
if (_themeSettingsType != null && _themeSettings is ISettingsControl themeSettingsControl)
{
await themeSettingsControl.UpdateSettings();
}
// default page properties
if (_insert != "=")
{
Page child;
@@ -688,7 +741,21 @@
_page.UpdateModulePermissions = bool.Parse(_updatemodulepermissions);
}
_page = await PageService.UpdatePageAsync(_page);
if (_copy)
{
// create page
_page = await PageService.AddPageAsync(_page);
await PageService.CopyPageAsync(_pageId, _page.PageId, bool.Parse(_updatemodulepermissions));
await logger.LogInformation("Page Added {Page}", _page);
}
else
{
// update page
_page = await PageService.UpdatePageAsync(_page);
await logger.LogInformation("Page Saved {Page}", _page);
}
// update page order
await PageService.UpdatePageOrderAsync(_page.SiteId, _page.PageId, _page.ParentId);
if (_currentparentid == string.Empty)
{
@@ -699,12 +766,6 @@
await PageService.UpdatePageOrderAsync(_page.SiteId, _page.PageId, int.Parse(_currentparentid));
}
if (_themeSettingsType != null && _themeSettings is ISettingsControl themeSettingsControl)
{
await themeSettingsControl.UpdateSettings();
}
await logger.LogInformation("Page Saved {Page}", _page);
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
{
NavigationManager.NavigateTo(PageState.ReturnUrl, true); // redirect to page being edited and reload

View File

@@ -3,10 +3,11 @@
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserService UserService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@inject ISettingService SettingService
@inject ITimeZoneService TimeZoneService
@inject ILanguageService LanguageService
@inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_initialized)
{
@@ -71,6 +72,18 @@
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="culture" HelpText="Your preferred language. Note that you will only be able to choose from languages supported on this site." ResourceKey="CultureCode">Language:</Label>
<div class="col-sm-9">
<select id="culture" class="form-select" @bind="@_culturecode">
<option value="">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@foreach (var language in _languages)
{
<option value="@language.Code">@language.Name</option>
}
</select>
</div>
</div>
</div>
<br />
<button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button>
@@ -95,6 +108,8 @@
@code {
private bool _initialized = false;
private List<Models.TimeZone> _timezones;
private IEnumerable<Models.Language> _languages;
private string _passwordrequirements;
private string _username = string.Empty;
private ElementReference form;
@@ -106,6 +121,7 @@
private string _email = string.Empty;
private string _displayname = string.Empty;
private string _timezoneid = string.Empty;
private string _culturecode = string.Empty;
private bool _userCreated = false;
private bool _allowsitelogin = true;
@@ -113,10 +129,13 @@
protected override async Task OnInitializedAsync()
{
_timezones = TimeZoneService.GetTimeZones();
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
_timezones = TimeZoneService.GetTimeZones();
_timezoneid = PageState.Site.TimeZoneId;
_culturecode = PageState.Site.CultureCode;
_initialized = true;
}
@@ -147,6 +166,7 @@
Email = _email,
DisplayName = (_displayname == string.Empty ? _username : _displayname),
TimeZoneId = _timezoneid,
CultureCode = _culturecode,
PhotoFileId = null
};
user = await UserService.AddUserAsync(user);

View File

@@ -11,12 +11,15 @@
@inject IThemeService ThemeService
@inject ISettingService SettingService
@inject ITimeZoneService TimeZoneService
@inject ILocalizationService LocalizationService
@inject IServiceProvider ServiceProvider
@inject IStringLocalizer<Index> Localizer
@inject INotificationService NotificationService
@inject IJobService JobService
@inject IStringLocalizer<SharedResources> SharedLocalizer
@inject IOutputCacheService CacheService
@inject ISiteGroupService SiteGroupService
@inject ISiteGroupMemberService SiteGroupMemberService
@if (_initialized)
{
@@ -29,22 +32,7 @@
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="homepage" HelpText="Select the home page for the site (to be used if there is no page with a path of '/')" ResourceKey="HomePage">Home Page: </Label>
<div class="col-sm-9">
<select id="homepage" class="form-select" @bind="@_homepageid" required>
<option value="-">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@foreach (Page page in _pages)
{
if (UserSecurity.ContainsRole(page.PermissionList, PermissionNames.View, RoleNames.Everyone))
{
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
}
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="timezone" HelpText="Your time zone" ResourceKey="TimeZone">Time Zone:</Label>
<Label Class="col-sm-3" For="timezone" HelpText="The default time zone for the site" ResourceKey="TimeZone">Time Zone:</Label>
<div class="col-sm-9">
<select id="timezone" class="form-select" @bind="@_timezoneid">
<option value="">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@@ -55,6 +43,18 @@
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="culture" HelpText="The default language of the site's content" ResourceKey="Culture">Language:</Label>
<div class="col-sm-9">
<select id="culture" class="form-select" @bind="@_culturecode">
<option value="">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@foreach (var culture in _cultures)
{
<option value="@culture.Name">@culture.DisplayName</option>
}
</select>
</div>
</div>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<div class="row mb-1 align-items-center">
@@ -333,57 +333,6 @@
</Section>
@if (_aliases != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<Section Name="Aliases" Heading="Aliases" ResourceKey="Aliases">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="aliases" HelpText="The urls for the site. This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or virtual folders (ie. domain.com/folder)." ResourceKey="Aliases">Aliases: </Label>
<div class="col-sm-9">
<button type="button" class="btn btn-primary" @onclick="AddAlias">@SharedLocalizer["Add"]</button>
<Pager Items="@_aliases">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["AliasName"]</th>
<th>@Localizer["AliasDefault"]</th>
</Header>
<Row>
@if (context.AliasId != _aliasid)
{
<td>
@if (_aliasid == -1)
{
<button type="button" class="btn btn-primary" @onclick="@(() => EditAlias(context))">@SharedLocalizer["Edit"]</button>
}
</td>
<td>
@if (_aliasid == -1)
{
<ActionDialog Action="Delete" OnClick="@(async () => await DeleteAlias(context))" ResourceKey="DeleteAlias" Class="btn btn-danger" Header="Delete Alias" Message="@string.Format(Localizer["Confirm.Alias.Delete", context.Name])" />
}
</td>
<td>@context.Name</td>
<td>@((context.IsDefault) ? SharedLocalizer["Yes"] : SharedLocalizer["No"])</td>
}
else
{
<td><button type="button" class="btn btn-success" @onclick="@(async () => await SaveAlias())">@SharedLocalizer["Save"]</button></td>
<td><button type="button" class="btn btn-secondary" @onclick="@(async () => await CancelAlias())">@SharedLocalizer["Cancel"]</button></td>
<td>
<input id="aliasname" class="form-control" @bind="@_aliasname" />
</td>
<td>
<select id="defaultalias" class="form-select" @bind="@_defaultalias" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</td>
}
</Row>
</Pager>
</div>
</div>
</div>
</Section>
<Section Name="Hosting" Heading="Hosting Model" ResourceKey="Hosting">
<div class="container">
<div class="row mb-1 align-items-center">
@@ -409,7 +358,7 @@
</div>
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="runtime" HelpText="The render mode for UI components which require interactivity" ResourceKey="Runtime">Interactivity: </Label>
<Label Class="col-sm-3" For="runtime" HelpText="The hosting model for UI components which require interactivity" ResourceKey="Runtime">Interactivity: </Label>
<div class="col-sm-9">
<select id="runtime" class="form-select" @bind="@_runtime" required>
<option value="@Runtimes.Server">@(SharedLocalizer["Runtime" + @Runtimes.Server])</option>
@@ -438,6 +387,180 @@
</div>
</div>
</Section>
<Section Name="Aliases" Heading="Urls" ResourceKey="Aliases">
<div class="container">
@if (!_addAlias)
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="aliases" HelpText="The urls for this site. This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or virtual folders (ie. domain.com/folder)." ResourceKey="Aliases">Urls: </Label>
<div class="col-sm-9">
<div class="input-group">
<select id="aliases" class="form-select" value="@_aliasid" @onchange="(e => AliasChanged(e))">
<option value="-1">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@foreach (var alias in _aliases)
{
<option value="@alias.AliasId">@alias.Name @((alias.IsDefault) ? "(" + Localizer["Default"] + ")" : "")</option>
}
</select>
@if (!_addAlias)
{
<button type="button" class="btn btn-primary" @onclick="AddAlias">@SharedLocalizer["Add"]</button>
}
</div>
</div>
</div>
}
@if (_aliasid != -1 || _addAlias)
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="aliasname" HelpText="A url for this site. This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or virtual folders (ie. domain.com/folder)." ResourceKey="AliasName">Url: </Label>
<div class="col-sm-9">
<input id="aliasname" class="form-control" @bind="@_aliasname" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultalias" HelpText="The default alias for the site. Requests for non-default aliases will be redirected to the default alias." ResourceKey="DefaultAlias">Default? </Label>
<div class="col-sm-9">
<select id="defaultalias" class="form-select" @bind="@_defaultalias">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
}
<div class="row mb-1 align-items-center">
<div class="col-sm-3"></div>
<div class="col-sm-9">
@if (_aliasid != -1 || _addAlias)
{
<button type="button" class="btn btn-success me-2" @onclick="SaveAlias">@SharedLocalizer["Save"]</button>
}
@if (_aliasid != -1 && !_addAlias)
{
<ActionDialog Action="Delete" OnClick="@(async () => await DeleteAlias())" ResourceKey="DeleteAlias" Class="btn btn-danger" Header="Delete Alias" Message="@string.Format(Localizer["Confirm.Alias.Delete", _aliasname])" />
}
@if (_addAlias)
{
<button type="button" class="btn btn-secondary" @onclick="CancelAlias">@SharedLocalizer["Cancel"]</button>
}
</div>
</div>
</div>
</Section>
<Section Name="SiteGroupMembers" Heading="Site Groups" ResourceKey="SiteGroupMembers">
<div class="container">
@if (!_addSiteGroup)
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="group" HelpText="The site groups in this tenant (database)" ResourceKey="SiteGroupMembers">Group: </Label>
<div class="col-sm-9">
<div class="input-group">
<select id="group" class="form-select" value="@_siteGroupId" @onchange="(e => SiteGroupChanged(e))">
<option value="-1">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@foreach (var siteGroup in _siteGroups)
{
<option value="@siteGroup.SiteGroupId">@siteGroup.Name</option>
}
</select>
@if (!_addSiteGroup)
{
<button type="button" class="btn btn-primary" @onclick="AddSiteGroup">@SharedLocalizer["Add"]</button>
}
</div>
</div>
</div>
}
@if (_siteGroupId != -1 || _addSiteGroup)
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="groupname" HelpText="Name of the site group" ResourceKey="GroupName">Name: </Label>
<div class="col-sm-9">
<input id="groupname" class="form-control" @bind="@_groupName" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="grouptype" HelpText="Defines the specific behavior of the site group" ResourceKey="GroupType">Type: </Label>
<div class="col-sm-9">
<select id="grouptype" class="form-select" @bind="@_groupType">
<option value="@SiteGroupTypes.Synchronization">@Localizer[@SiteGroupTypes.Synchronization]</option>
<option value="@SiteGroupTypes.ChangeDetection">@Localizer[@SiteGroupTypes.ChangeDetection]</option>
<option value="@SiteGroupTypes.Localization">@Localizer[SiteGroupTypes.Localization]</option>
</select>
</div>
</div>
}
@if (_siteGroupId != -1)
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="site" HelpText="The sites which are members of this site group" ResourceKey="Site">Members: </Label>
<div class="col-sm-9">
<div class="input-group">
<select id="site" class="form-select" value="@_siteId" @onchange="(e => SiteChanged(e))">
<option value="-1">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@foreach (var site in _sites)
{
<option value="@site.SiteId">@site.Name</option>
}
</select>
@if (!_addSiteGroupMember)
{
<button type="button" class="btn btn-primary" @onclick="AddSiteGroupMember">@SharedLocalizer["Add"]</button>
}
else
{
<button type="button" class="btn btn-primary" @onclick="AddSiteGroupMember">@SharedLocalizer["Select"]</button>
}
</div>
</div>
</div>
}
@if (_siteGroupId != -1 && _siteId != -1)
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="primary" HelpText="Indicates if the selected member is the primary site of the site group" ResourceKey="Primary">Primary? </Label>
<div class="col-sm-9">
<select id="primary" class="form-select" @bind="@_primary">
<option value="False">@SharedLocalizer["No"]</option>
<option value="True">@SharedLocalizer["Yes"]</option>
</select>
</div>
</div>
@if (_primary == "False" && !string.IsNullOrEmpty(_synchronized) && (_groupType == SiteGroupTypes.Synchronization || _groupType == SiteGroupTypes.ChangeDetection))
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="synchronized" HelpText="The date/time when the site was last synchronized" ResourceKey="Synchronized">Synchronized: </Label>
<div class="col-sm-9">
<div class="input-group">
<input id="synchronized" class="form-control" @bind="@_synchronized" disabled />
@if (!string.IsNullOrEmpty(_synchronized))
{
<button type="button" class="btn btn-primary" @onclick="ResetSiteGroupMember">@SharedLocalizer["Reset"]</button>
}
</div>
</div>
</div>
}
}
<div class="row mb-1 align-items-center">
<div class="col-sm-3"></div>
<div class="col-sm-9">
@if ((_siteGroupId != -1 || _addSiteGroup))
{
<button type="button" class="btn btn-success me-2" @onclick="SaveSiteGroupMember">@SharedLocalizer["Save"]</button>
}
@if (_siteGroupId != -1 && !_addSiteGroup && _siteId != -1 && !_addSiteGroupMember)
{
<ActionDialog Action="Delete" OnClick="@(async () => await DeleteSiteGroupMember())" ResourceKey="DeleteSiteGroupMember" Class="btn btn-danger" Header="Delete Site Group" Message="@Localizer["Confirm.SiteGroupMember.Delete"]" />
}
@if (_addSiteGroup)
{
<button type="button" class="btn btn-secondary" @onclick="CancelSiteGroupMember">@SharedLocalizer["Cancel"]</button>
}
</div>
</div>
</div>
</Section>
<Section Name="TenantInformation" Heading="Database" ResourceKey="TenantInformation">
<div class="container">
<div class="row mb-1 align-items-center">
@@ -478,10 +601,11 @@
private List<ThemeControl> _containers = new List<ThemeControl>();
private List<Page> _pages;
private List<Models.TimeZone> _timezones;
private IEnumerable<Models.Culture> _cultures;
private string _name = string.Empty;
private string _homepageid = "-";
private string _timezoneid = string.Empty;
private string _culturecode = string.Empty;
private string _isdeleted;
private string _sitemap = "";
private string _siteguid = "";
@@ -527,17 +651,29 @@
private int _pwasplashiconfileid = -1;
private FileManager _pwasplashiconfilemanager;
private List<Alias> _aliases;
private int _aliasid = -1;
private string _aliasname;
private string _defaultalias;
private string _rendermode = RenderModes.Interactive;
private string _enhancednavigation = "True";
private string _runtime = Runtimes.Server;
private string _prerender = "True";
private string _hybrid = "False";
private List<Alias> _aliases;
private int _aliasid = -1;
private string _aliasname;
private string _defaultalias;
private bool _addAlias = false;
private List<SiteGroup> _siteGroups = new List<SiteGroup>();
private List<Site> _sites = new List<Site>();
private int _siteGroupId = -1;
private int _siteId;
private string _groupName = string.Empty;
private string _groupType = SiteGroupTypes.Synchronization;
private string _primary = "True";
private string _synchronized = string.Empty;
private bool _addSiteGroup = false;
private bool _addSiteGroupMember = false;
private string _tenant = string.Empty;
private string _database = string.Empty;
private string _connectionstring = string.Empty;
@@ -564,16 +700,14 @@
if (site != null)
{
_timezones = TimeZoneService.GetTimeZones();
_cultures = await LocalizationService.GetNeutralCulturesAsync();
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
_name = site.Name;
_timezoneid = site.TimeZoneId;
if (site.HomePageId != null)
{
_homepageid = site.HomePageId.Value.ToString();
}
_culturecode = site.CultureCode;
_isdeleted = site.IsDeleted.ToString();
_sitemap = PageState.Alias.Protocol + PageState.Alias.Name + "/sitemap.xml";
_siteguid = site.SiteGuid;
@@ -652,7 +786,7 @@
}
// aliases
await GetAliases();
await LoadAliases();
// hosting model
_rendermode = site.RenderMode;
@@ -676,6 +810,12 @@
}
}
// site groups
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
await LoadSiteGroups();
}
// audit
_createdby = site.CreatedBy;
_createdon = site.CreatedOn;
@@ -743,7 +883,7 @@
{
site.Name = _name;
site.TimeZoneId = _timezoneid;
site.HomePageId = (_homepageid != "-" ? int.Parse(_homepageid) : null);
site.CultureCode = _culturecode;
site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
// appearance
@@ -992,95 +1132,100 @@
}
}
private async Task GetAliases()
private async Task LoadAliases()
{
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
_aliases = await AliasService.GetAliasesAsync();
_aliases = _aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Alias.TenantId).OrderBy(item => item.AliasId).ToList();
_aliasid = -1;
_addAlias = false;
}
private async void AliasChanged(ChangeEventArgs e)
{
_aliasid = int.Parse(e.Value.ToString());
if (_aliasid != -1)
{
_aliases = await AliasService.GetAliasesAsync();
_aliases = _aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Alias.TenantId).OrderBy(item => item.AliasId).ToList();
var alias = _aliases.FirstOrDefault(item => item.AliasId == _aliasid);
if (alias != null)
{
_aliasname = alias.Name;
_defaultalias = alias.IsDefault.ToString();
}
}
else
{
_aliasname = "";
_defaultalias = "False";
}
StateHasChanged();
}
private void AddAlias()
{
_aliases.Add(new Alias { AliasId = 0, Name = "", IsDefault = false });
_aliasid = 0;
_aliasid = -1;
_aliasname = "";
_defaultalias = "False";
_addAlias = true;
StateHasChanged();
}
private void EditAlias(Alias alias)
private async Task DeleteAlias()
{
_aliasid = alias.AliasId;
_aliasname = alias.Name;
_defaultalias = alias.IsDefault.ToString();
await AliasService.DeleteAliasAsync(_aliasid);
await LoadAliases();
StateHasChanged();
}
private async Task DeleteAlias(Alias alias)
{
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
await AliasService.DeleteAliasAsync(alias.AliasId);
await GetAliases();
StateHasChanged();
}
}
private async Task SaveAlias()
{
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
if (!string.IsNullOrEmpty(_aliasname))
{
if (!string.IsNullOrEmpty(_aliasname))
var aliases = await AliasService.GetAliasesAsync();
int protocolIndex = _aliasname.IndexOf("://", StringComparison.OrdinalIgnoreCase);
if (protocolIndex != -1)
{
var aliases = await AliasService.GetAliasesAsync();
_aliasname = _aliasname.Substring(protocolIndex + 3);
}
int protocolIndex = _aliasname.IndexOf("://", StringComparison.OrdinalIgnoreCase);
if (protocolIndex != -1)
var alias = aliases.FirstOrDefault(item => item.Name == _aliasname);
bool unique = (alias == null || alias.AliasId == _aliasid);
if (unique)
{
if (_aliasid == 0)
{
_aliasname = _aliasname.Substring(protocolIndex + 3);
alias = new Alias { SiteId = PageState.Site.SiteId, TenantId = PageState.Alias.TenantId, Name = _aliasname, IsDefault = bool.Parse(_defaultalias) };
await AliasService.AddAliasAsync(alias);
}
var alias = aliases.FirstOrDefault(item => item.Name == _aliasname);
bool unique = (alias == null || alias.AliasId == _aliasid);
if (unique)
else
{
if (_aliasid == 0)
alias = _aliases.SingleOrDefault(item => item.AliasId == _aliasid);
if (alias != null)
{
alias = new Alias { SiteId = PageState.Site.SiteId, TenantId = PageState.Alias.TenantId, Name = _aliasname, IsDefault = bool.Parse(_defaultalias) };
await AliasService.AddAliasAsync(alias);
}
else
{
alias = _aliases.SingleOrDefault(item => item.AliasId == _aliasid);
if (alias != null)
{
alias.Name = _aliasname;
alias.IsDefault = bool.Parse(_defaultalias);
await AliasService.UpdateAliasAsync(alias);
}
alias.Name = _aliasname;
alias.IsDefault = bool.Parse(_defaultalias);
await AliasService.UpdateAliasAsync(alias);
}
}
await GetAliases();
_aliasid = -1;
_aliasname = "";
StateHasChanged();
}
else // Duplicate alias
{
AddModuleMessage(Localizer["Message.Aliases.Taken"], MessageType.Warning);
await ScrollToPageTop();
}
await LoadAliases();
_aliasid = -1;
_aliasname = "";
StateHasChanged();
}
else // Duplicate alias
{
AddModuleMessage(Localizer["Message.Aliases.Taken"], MessageType.Warning);
await ScrollToPageTop();
}
}
}
private async Task CancelAlias()
{
await GetAliases();
await LoadAliases();
_aliasid = -1;
_aliasname = "";
StateHasChanged();
@@ -1090,4 +1235,202 @@
await CacheService.EvictByTag(Constants.SitemapOutputCacheTag);
AddModuleMessage(Localizer["Success.SiteMap.CacheEvicted"], MessageType.Success);
}
private async Task LoadSiteGroups()
{
_siteGroups = await SiteGroupService.GetSiteGroupsAsync();
_siteGroupId = -1;
_addSiteGroup = false;
StateHasChanged();
}
private async Task SiteGroupChanged(ChangeEventArgs e)
{
_siteGroupId = int.Parse(e.Value.ToString());
if (_siteGroupId != -1)
{
var group = _siteGroups.FirstOrDefault(item => item.SiteGroupId == _siteGroupId);
if (group != null)
{
_groupName = group.Name;
_groupType = group.Type;
_siteId = -1;
_primary = "False";
_addSiteGroupMember = false;
await LoadSites();
}
}
StateHasChanged();
}
private async Task LoadSites()
{
var siteGroupMembers = await SiteGroupMemberService.GetSiteGroupMembersAsync(-1, _siteGroupId);
_sites = await SiteService.GetSitesAsync();
if (_addSiteGroupMember)
{
// include sites which are not members
_sites = _sites.ExceptBy(siteGroupMembers.Select(item => item.SiteId), item => item.SiteId).ToList();
}
else
{
// include sites which are members
_sites = _sites.Where(item => siteGroupMembers.Any(item2 => item2.SiteId == item.SiteId)).ToList();
var group = _siteGroups.FirstOrDefault(item => item.SiteGroupId == _siteGroupId);
foreach (var site in _sites)
{
if (group.PrimarySiteId == site.SiteId)
{
site.Name += $" ({Localizer["Primary"]})";
}
else
{
site.Name += $" ({Localizer["Secondary"]})";
}
}
var siteGroupMember = siteGroupMembers.FirstOrDefault(item => item.SiteId == _siteId);
if (siteGroupMember != null)
{
_primary = (siteGroupMember.SiteGroup.PrimarySiteId == _siteId) ? "True" : "False";
_synchronized = UtcToLocal(siteGroupMember.SynchronizedOn).ToString();
}
}
}
private async Task SiteChanged(ChangeEventArgs e)
{
_siteId = int.Parse(e.Value.ToString());
var siteGroupMember = await SiteGroupMemberService.GetSiteGroupMemberAsync(_siteId, _siteGroupId);
if (siteGroupMember != null)
{
_primary = (siteGroupMember.SiteGroup.PrimarySiteId == _siteId) ? "True" : "False";
_synchronized = UtcToLocal(siteGroupMember.SynchronizedOn).ToString();
}
StateHasChanged();
}
private async Task AddSiteGroup()
{
_groupName = "";
_siteId = PageState.Site.SiteId;
_primary = "True";
_synchronized = "";
_addSiteGroup = true;
}
private async Task AddSiteGroupMember()
{
_addSiteGroupMember = !_addSiteGroupMember;
_siteId = -1;
await LoadSites();
}
private async Task SaveSiteGroupMember()
{
if (string.IsNullOrEmpty(_groupName))
{
AddModuleMessage(Localizer["Message.Required.GroupName"], MessageType.Warning);
await ScrollToPageTop();
return;
}
SiteGroup siteGroup = null;
if (_siteGroupId == -1)
{
siteGroup = new SiteGroup
{
Name = _groupName,
Type = _groupType,
PrimarySiteId = _siteId,
Synchronize = false
};
siteGroup = await SiteGroupService.AddSiteGroupAsync(siteGroup);
}
else
{
siteGroup = _siteGroups.FirstOrDefault(item => item.SiteGroupId == _siteGroupId);
if (siteGroup != null)
{
siteGroup.Name = _groupName;
siteGroup.Type = _groupType;
siteGroup.PrimarySiteId = (_primary == "True") ? _siteId : siteGroup.PrimarySiteId;
siteGroup = await SiteGroupService.UpdateSiteGroupAsync(siteGroup);
}
else
{
siteGroup = null;
}
}
if (siteGroup != null)
{
if (_siteId != -1)
{
var siteGroupMember = await SiteGroupMemberService.GetSiteGroupMemberAsync(_siteId, siteGroup.SiteGroupId);
if (siteGroupMember == null)
{
siteGroupMember = new SiteGroupMember
{
SiteGroupId = siteGroup.SiteGroupId,
SiteId = _siteId
};
await SiteGroupMemberService.AddSiteGroupMemberAsync(siteGroupMember);
}
else
{
siteGroupMember.SynchronizedOn = string.IsNullOrEmpty(_synchronized) ? null : siteGroupMember.SynchronizedOn;
await SiteGroupMemberService.UpdateSiteGroupMemberAsync(siteGroupMember);
}
}
if (siteGroup.Type == SiteGroupTypes.Synchronization)
{
// enable synchronization job if it is not enabled already
var jobs = await JobService.GetJobsAsync();
var job = jobs.FirstOrDefault(item => item.JobType == "Oqtane.Infrastructure.SynchronizationJob, Oqtane.Server");
if (job != null && !job.IsEnabled)
{
job.IsEnabled = true;
await JobService.UpdateJobAsync(job);
}
}
await LoadSiteGroups();
}
}
private async Task CancelSiteGroupMember()
{
_groupName = "";
await LoadSiteGroups();
}
private async Task DeleteSiteGroupMember()
{
if (_siteGroupId != -1)
{
var siteGroupMember = await SiteGroupMemberService.GetSiteGroupMemberAsync(PageState.Site.SiteId, _siteGroupId);
if (siteGroupMember != null)
{
await SiteGroupMemberService.DeleteSiteGroupMemberAsync(siteGroupMember.SiteGroupId);
}
var siteGroupMembers = await SiteGroupMemberService.GetSiteGroupMembersAsync(-1, _siteGroupId);
if (!siteGroupMembers.Any())
{
await SiteGroupService.DeleteSiteGroupAsync(_siteGroupId);
}
await LoadSiteGroups();
}
}
private async Task ResetSiteGroupMember()
{
_synchronized = "";
}
}

View File

@@ -6,7 +6,6 @@
@inject ITenantService TenantService
@inject IAliasService AliasService
@inject ISiteService SiteService
@inject IThemeService ThemeService
@inject ISiteTemplateService SiteTemplateService
@inject IUserService UserService
@inject IInstallationService InstallationService
@@ -29,33 +28,9 @@ else
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="alias" HelpText="The urls for the site (comman delimited). This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or virtual folders (ie. domain.com/folder)." ResourceKey="Aliases">Urls: </Label>
<Label Class="col-sm-3" For="alias" HelpText="The primary url for the site. This can be a domain name (ie. domain.com), subdomain (ie. sub.domain.com) or a virtual folder (ie. domain.com/folder)." ResourceKey="Aliases">Url: </Label>
<div class="col-sm-9">
<textarea id="alias" class="form-control" @bind="@_urls" rows="3" required></textarea>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultTheme" HelpText="Select the default theme for the website" ResourceKey="DefaultTheme">Default Theme: </Label>
<div class="col-sm-9">
<select id="defaultTheme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
<option value="-">&lt;@Localizer["Theme.Select"]&gt;</option>
@foreach (var theme in _themes)
{
<option value="@theme.TypeName">@theme.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
<div class="col-sm-9">
<select id="defaultContainer" class="form-select" @bind="@_containertype" required>
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
<input id="alias" class="form-control" @bind="@_urls" required></input>
</div>
</div>
<div class="row mb-1 align-items-center">
@@ -70,26 +45,6 @@ else
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="rendermode" HelpText="The default render mode for the site" ResourceKey="Rendermode">Render Mode: </Label>
<div class="col-sm-9">
<select id="rendermode" class="form-select" @bind="@_rendermode" required>
<option value="@RenderModes.Interactive">@(SharedLocalizer["RenderMode" + @RenderModes.Interactive])</option>
<option value="@RenderModes.Static">@(SharedLocalizer["RenderMode" + @RenderModes.Static])</option>
<option value="@RenderModes.Headless">@(SharedLocalizer["RenderMode" + @RenderModes.Headless])</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="runtime" HelpText="The render mode for UI components which require interactivity" ResourceKey="Runtime">Interactivity: </Label>
<div class="col-sm-9">
<select id="runtime" class="form-select" @bind="@_runtime" required>
<option value="@Runtimes.Server">@(SharedLocalizer["Runtime" + @Runtimes.Server])</option>
<option value="@Runtimes.WebAssembly">@(SharedLocalizer["Runtime" + @Runtimes.WebAssembly])</option>
<option value="@Runtimes.Auto">@(SharedLocalizer["Runtime" + @Runtimes.Auto])</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="tenant" HelpText="Select the database for the site" ResourceKey="Tenant">Database: </Label>
<div class="col-sm-9">
@@ -186,9 +141,6 @@ else
private bool _showConnectionString = false;
private string _connectionString = string.Empty;
private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private List<SiteTemplate> _siteTemplates;
private List<Tenant> _tenants;
private string _tenantid = "-";
@@ -200,11 +152,7 @@ else
private string _name = string.Empty;
private string _urls = string.Empty;
private string _themetype = "-";
private string _containertype = "-";
private string _sitetemplatetype = "-";
private string _rendermode = RenderModes.Static;
private string _runtime = Runtimes.Server;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@@ -215,19 +163,11 @@ else
{
_tenantid = _tenants.First(item => item.Name == TenantNames.Master).TenantId.ToString();
}
_urls = PageState.Alias.Name;
_themeList = await ThemeService.GetThemesAsync(PageState.Site.SiteId);
_themes = ThemeService.GetThemeControls(_themeList);
if (_themes.Any(item => item.TypeName == Constants.DefaultTheme))
{
_themetype = Constants.DefaultTheme;
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = _containers.First().TypeName;
}
_urls = PageState.Alias.Name + "/sitename";
_siteTemplates = await SiteTemplateService.GetSiteTemplatesAsync();
if (_siteTemplates.Any(item => item.TypeName == Constants.DefaultSiteTemplate))
if (_siteTemplates.Any(item => item.TypeName == Constants.EmptySiteTemplate))
{
_sitetemplatetype = Constants.DefaultSiteTemplate;
_sitetemplatetype = Constants.EmptySiteTemplate;
}
_databases = await DatabaseService.GetDatabasesAsync();
@@ -281,37 +221,13 @@ else
StateHasChanged();
}
private async void ThemeChanged(ChangeEventArgs e)
{
try
{
_themetype = (string)e.Value;
if (_themetype != "-")
{
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = _containers.First().TypeName;
}
else
{
_containers = new List<ThemeControl>();
_containertype = "-";
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Containers For Theme {ThemeType} {Error}", _themetype, ex.Message);
AddModuleMessage(Localizer["Error.Theme.LoadContainers"], MessageType.Error);
}
}
private async Task SaveSite()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-" && _sitetemplatetype != "-")
if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && _sitetemplatetype != "-")
{
_urls = Regex.Replace(_urls, @"\r\n?|\n", ",");
var duplicates = new List<string>();
@@ -399,12 +315,12 @@ else
{
config.SiteName = _name;
config.Aliases = _urls;
config.DefaultTheme = _themetype;
config.DefaultContainer = _containertype;
config.DefaultTheme = Constants.DefaultTheme;
config.DefaultContainer = Constants.DefaultContainer;
config.DefaultAdminContainer = "";
config.SiteTemplate = _sitetemplatetype;
config.RenderMode = _rendermode;
config.Runtime = _runtime;
config.RenderMode = RenderModes.Static;
config.Runtime = Runtimes.Server;
config.Register = false;
ShowProgressIndicator();

View File

@@ -30,6 +30,7 @@
</div>
</div>
</form>
<br />
<Section Name="Information" ResourceKey="Information" Heading="Information">
<div class="container">
<div class="row mb-1 align-items-center">
@@ -81,6 +82,12 @@
}
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="fingerprint" HelpText="A unique identifier for the theme's static resources. This value can be changed by clicking the Save option below (ie. cache busting)." ResourceKey="Fingerprint">Fingerprint: </Label>
<div class="col-sm-9">
<input id="fingerprint" class="form-control" @bind="@_fingerprint" disabled />
</div>
</div>
</div>
</Section>
<br />
@@ -117,6 +124,7 @@
private string _url = "";
private string _contact = "";
private string _license = "";
private string _fingerprint = "";
private List<Permission> _permissions = null;
private string _createdby;
private DateTime _createdon;
@@ -143,6 +151,7 @@
_url = theme.Url;
_contact = theme.Contact;
_license = theme.License;
_fingerprint = theme.Fingerprint;
_permissions = theme.PermissionList;
_createdby = theme.CreatedBy;
_createdon = theme.CreatedOn;

View File

@@ -10,6 +10,7 @@
@inject IFileService FileService
@inject IFolderService FolderService
@inject ITimeZoneService TimeZoneService
@inject ILanguageService LanguageService
@inject IJSRuntime jsRuntime
@inject IServiceProvider ServiceProvider
@inject IStringLocalizer<Index> Localizer
@@ -58,6 +59,18 @@
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="culture" HelpText="Your preferred language. Note that you will only be able to choose from languages supported on this site." ResourceKey="CultureCode">Language:</Label>
<div class="col-sm-9">
<select id="culture" class="form-select" @bind="@_culturecode">
<option value="">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@foreach (var language in _languages)
{
<option value="@language.Code">@language.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="@_photofileid.ToString()" HelpText="A photo of yourself" ResourceKey="Photo"></Label>
<div class="col-sm-9">
@@ -448,6 +461,9 @@
@code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
private List<Models.TimeZone> _timezones;
private IEnumerable<Language> _languages;
private bool _initialized = false;
private bool _allowtwofactor = false;
private bool _allowpasskeys = false;
@@ -458,8 +474,8 @@
private string _displayname = string.Empty;
private FileManager _filemanager;
private int _folderid = -1;
private List<Models.TimeZone> _timezones;
private string _timezoneid = string.Empty;
private string _culturecode = string.Empty;
private int _photofileid = -1;
private File _photo = null;
private string _imagefiles = string.Empty;
@@ -493,12 +509,15 @@
if (PageState.User != null)
{
_timezones = TimeZoneService.GetTimeZones();
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
// identity section
_username = PageState.User.Username;
_email = PageState.User.Email;
_displayname = PageState.User.DisplayName;
_timezones = TimeZoneService.GetTimeZones();
_timezoneid = PageState.User.TimeZoneId;
_culturecode = PageState.User.CultureCode;
var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath);
if (folder != null)
{
@@ -572,6 +591,7 @@
user.Email = _email;
user.DisplayName = (_displayname == string.Empty ? _username : _displayname);
user.TimeZoneId = _timezoneid;
user.CultureCode = _culturecode;
user.PhotoFileId = _filemanager.GetFileId();
if (user.PhotoFileId == -1)
{

View File

@@ -6,6 +6,7 @@
@inject IProfileService ProfileService
@inject ISettingService SettingService
@inject ITimeZoneService TimeZoneService
@inject ILanguageService LanguageService
@inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@@ -55,6 +56,18 @@
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="culture" HelpText="The user's preferred language. Note that you will only be able to choose from languages supported on this site." ResourceKey="CultureCode">Language:</Label>
<div class="col-sm-9">
<select id="culture" class="form-select" @bind="@_culturecode">
<option value="">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@foreach (var language in _languages)
{
<option value="@language.Code">@language.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="notify" HelpText="Indicate if new users should receive an email notification" ResourceKey="Notify">Notify? </Label>
<div class="col-sm-9">
@@ -129,12 +142,15 @@
@code {
private List<Models.TimeZone> _timezones;
private IEnumerable<Models.Language> _languages;
private bool _initialized = false;
private string _username = string.Empty;
private string _email = string.Empty;
private string _confirmed = "True";
private string _displayname = string.Empty;
private string _timezoneid = string.Empty;
private string _culturecode = string.Empty;
private string _notify = "True";
private List<Profile> _profiles;
private Dictionary<string, string> _settings;
@@ -147,6 +163,11 @@
try
{
_timezones = TimeZoneService.GetTimeZones();
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
_timezoneid = PageState.Site.TimeZoneId;
_culturecode = PageState.Site.CultureCode;
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
foreach (var profile in _profiles)
{
@@ -157,8 +178,9 @@
profile.Options = string.Join(",", options.OrderBy(item => item.Value).Select(kvp => $"{kvp.Key}:{kvp.Value}"));
}
}
_settings = new Dictionary<string, string>();
_timezoneid = PageState.Site.TimeZoneId;
_initialized = true;
}
catch (Exception ex)
@@ -194,6 +216,7 @@
user.EmailConfirmed = bool.Parse(_confirmed);
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
user.TimeZoneId = _timezoneid;
user.CultureCode = _culturecode;
user.PhotoFileId = null;
user.SuppressNotification = !bool.Parse(_notify);

View File

@@ -7,6 +7,7 @@
@inject ISettingService SettingService
@inject IFileService FileService
@inject ITimeZoneService TimeZoneService
@inject ILanguageService LanguageService
@inject IServiceProvider ServiceProvider
@inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@@ -55,6 +56,18 @@
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="culture" HelpText="The user's preferred language. Note that you will only be able to choose from languages supported on this site." ResourceKey="CultureCode">Language:</Label>
<div class="col-sm-9">
<select id="culture" class="form-select" @bind="@_culturecode">
<option value="">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
@foreach (var language in _languages)
{
<option value="@language.Code">@language.Name</option>
}
</select>
</div>
</div>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<div class="row mb-1 align-items-center">
@@ -211,7 +224,7 @@
{
<button type="button" class="btn btn-primary ms-1" @onclick="ImpersonateUser">@Localizer["Impersonate"]</button>
}
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _isdeleted == "True")
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _candelete)
{
<ActionDialog Header="Delete User" Message="Are You Sure You Wish To Permanently Delete This User?" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger ms-1" OnClick="@(async () => await DeleteUser())" ResourceKey="DeleteUser" />
}
@@ -224,17 +237,21 @@
private bool _allowpasskeys = false;
private bool _allowexternallogin = false;
private List<Models.TimeZone> _timezones;
private IEnumerable<Language> _languages;
private int _userid;
private string _username = string.Empty;
private string _email = string.Empty;
private string _confirmed = string.Empty;
private string _displayname = string.Empty;
private List<Models.TimeZone> _timezones;
private string _timezoneid = string.Empty;
private string _culturecode = string.Empty;
private string _isdeleted;
private string _lastlogin;
private string _lastipaddress;
private bool _ishost = false;
private bool _candelete = false;
private string _passwordrequirements;
private string _password = string.Empty;
@@ -270,13 +287,17 @@
var user = await UserService.GetUserAsync(_userid, PageState.Site.SiteId);
if (user != null)
{
_timezones = TimeZoneService.GetTimeZones();
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
_username = user.Username;
_email = user.Email;
_confirmed = user.EmailConfirmed.ToString();
_displayname = user.DisplayName;
_timezones = TimeZoneService.GetTimeZones();
_timezoneid = PageState.User.TimeZoneId;
_culturecode = PageState.User.CultureCode;
_isdeleted = user.IsDeleted.ToString();
_candelete = user.IsDeleted;
_lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", UtcToLocal(user.LastLoginOn));
_lastipaddress = user.LastIPAddress;
_ishost = UserSecurity.ContainsRole(user.Roles, RoleNames.Host);
@@ -344,6 +365,7 @@
user.EmailConfirmed = bool.Parse(_confirmed);
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
user.TimeZoneId = _timezoneid;
user.CultureCode = _culturecode;
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
user.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));

View File

@@ -1,7 +1,7 @@
@namespace Oqtane.Modules.Admin.Users
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserService UserService
@inject ISiteTaskService SiteTaskService
@inject IStringLocalizer<Users> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@@ -43,17 +43,9 @@
var fileid = _filemanager.GetFileId();
if (fileid != -1)
{
ShowProgressIndicator();
var results = await UserService.ImportUsersAsync(PageState.Site.SiteId, fileid, bool.Parse(_notify));
if (bool.Parse(results["Success"]))
{
AddModuleMessage(string.Format(Localizer["Message.Import.Success"], results["Users"]), MessageType.Success);
}
else
{
AddModuleMessage(Localizer["Message.Import.Failure"], MessageType.Error);
}
HideProgressIndicator();
var siteTask = new SiteTask(PageState.Site.SiteId, "Import Users", "Oqtane.Infrastructure.ImportUsersTask, Oqtane.Server", $"{fileid}:{_notify}");
await SiteTaskService.AddSiteTaskAsync(siteTask);
AddModuleMessage(Localizer["Message.Import.Success"], MessageType.Success);
}
else
{

View File

@@ -61,6 +61,12 @@
{
<input type="file" id="@_fileinputid" name="file" accept="@_filter" />
}
@if (MaxUploadFileSize > 0)
{
<div class="row my-1">
<small class="fw-light">@string.Format(Localizer["File.MaxSize"], MaxUploadFileSize)</small>
</div>
}
</div>
<div class="col-auto">
<button type="button" class="btn btn-success" @onclick="UploadFiles">@SharedLocalizer["Upload"]</button>
@@ -163,6 +169,9 @@
[Parameter]
public int ChunkSize { get; set; } = 1; // optional - size of file chunks to upload in MB
[Parameter]
public int MaxUploadFileSize { get; set; } = -1; // optional - maximum upload file size in MB
[Parameter]
public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded
@@ -381,16 +390,39 @@
if (uploads.Length > 0)
{
string restricted = "";
string tooLarge = "";
foreach (var upload in uploads)
{
var filename = upload.Split(':')[0];
var fileparts = upload.Split(':');
var filename = fileparts[0];
if (MaxUploadFileSize > 0)
{
var filesizeBytes = long.Parse(fileparts[1]);
var filesizeMB = (double)filesizeBytes / (1024 * 1024);
if (filesizeMB > MaxUploadFileSize)
{
tooLarge += (tooLarge == "" ? "" : ",") + filename;
}
}
var extension = (filename.LastIndexOf(".") != -1) ? filename.Substring(filename.LastIndexOf(".") + 1) : "";
if (!PageState.Site.UploadableFiles.Split(',').Contains(extension.ToLower()))
{
restricted += (restricted == "" ? "" : ",") + extension;
}
}
if (restricted == "")
if (restricted != "")
{
_message = string.Format(Localizer["Message.File.Restricted"], restricted);
_messagetype = MessageType.Warning;
}
else if (tooLarge != "")
{
_message = string.Format(Localizer["Message.File.TooLarge"], tooLarge, MaxUploadFileSize);
_messagetype = MessageType.Warning;
}
else
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
@@ -490,11 +522,6 @@
tokenSource.Dispose();
}
}
else
{
_message = string.Format(Localizer["Message.File.Restricted"], restricted);
_messagetype = MessageType.Warning;
}
}
else
{

View File

@@ -31,8 +31,8 @@
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["CreatedOn"]</th>
<th>@SharedLocalizer["CreatedBy"]</th>
<th>@Localizer["CreatedOn"]</th>
<th>@Localizer["CreatedBy"]</th>
</Header>
<Row>
<td><ActionLink Action="View" Security="SecurityAccessLevel.Edit" OnClick="@(async () => await View(context))" ResourceKey="View" /></td>
@@ -122,13 +122,9 @@
{
try
{
htmltext = await HtmlTextService.GetHtmlTextAsync(htmltext.HtmlTextId, htmltext.ModuleId);
if (htmltext != null)
{
_view = htmltext.Content;
_view = Utilities.FormatContent(_view, PageState.Alias, "render");
StateHasChanged();
}
_view = htmltext.Content;
_view = Utilities.FormatContent(_view, PageState.Alias, "render");
StateHasChanged();
}
catch (Exception ex)
{
@@ -141,19 +137,15 @@
{
try
{
htmltext = await HtmlTextService.GetHtmlTextAsync(htmltext.HtmlTextId, ModuleState.ModuleId);
if (htmltext != null)
{
var content = htmltext.Content;
htmltext = new HtmlText();
htmltext.ModuleId = ModuleState.ModuleId;
htmltext.Content = content;
await HtmlTextService.AddHtmlTextAsync(htmltext);
await logger.LogInformation("Content Restored {HtmlText}", htmltext);
AddModuleMessage(Localizer["Message.Content.Restored"], MessageType.Success);
await LoadContent();
StateHasChanged();
}
var content = htmltext.Content;
htmltext = new HtmlText();
htmltext.ModuleId = ModuleState.ModuleId;
htmltext.Content = content;
await HtmlTextService.AddHtmlTextAsync(htmltext);
await logger.LogInformation("Content Restored {HtmlText}", htmltext);
AddModuleMessage(Localizer["Message.Content.Restored"], MessageType.Success);
await LoadContent();
StateHasChanged();
}
catch (Exception ex)
{
@@ -166,15 +158,11 @@
{
try
{
htmltext = await HtmlTextService.GetHtmlTextAsync(htmltext.HtmlTextId, ModuleState.ModuleId);
if (htmltext != null)
{
await HtmlTextService.DeleteHtmlTextAsync(htmltext.HtmlTextId, htmltext.ModuleId);
await logger.LogInformation("Content Deleted {HtmlText}", htmltext);
AddModuleMessage(Localizer["Message.Content.Deleted"], MessageType.Success);
await LoadContent();
StateHasChanged();
}
await HtmlTextService.DeleteHtmlTextAsync(htmltext.HtmlTextId, htmltext.ModuleId);
await logger.LogInformation("Content Deleted {HtmlText}", htmltext);
AddModuleMessage(Localizer["Message.Content.Deleted"], MessageType.Success);
await LoadContent();
StateHasChanged();
}
catch (Exception ex)
{

View File

@@ -7,6 +7,18 @@ using Oqtane.Shared;
namespace Oqtane.Modules.HtmlText.Services
{
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
public interface IHtmlTextService
{
Task<List<Models.HtmlText>> GetHtmlTextsAsync(int moduleId);
Task<Models.HtmlText> GetHtmlTextAsync(int moduleId);
Task<Models.HtmlText> AddHtmlTextAsync(Models.HtmlText htmltext);
Task DeleteHtmlTextAsync(int htmlTextId, int moduleId);
}
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
public class HtmlTextService : ServiceBase, IHtmlTextService, IClientService
{
@@ -24,11 +36,6 @@ namespace Oqtane.Modules.HtmlText.Services
return await GetJsonAsync<Models.HtmlText>(CreateAuthorizationPolicyUrl($"{ApiUrl}/{moduleId}", EntityNames.Module, moduleId));
}
public async Task<Models.HtmlText> GetHtmlTextAsync(int htmlTextId, int moduleId)
{
return await GetJsonAsync<Models.HtmlText>(CreateAuthorizationPolicyUrl($"{ApiUrl}/{htmlTextId}/{moduleId}", EntityNames.Module, moduleId));
}
public async Task<Models.HtmlText> AddHtmlTextAsync(Models.HtmlText htmlText)
{
return await PostJsonAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}", EntityNames.Module, htmlText.ModuleId), htmlText);

View File

@@ -1,20 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Oqtane.Documentation;
namespace Oqtane.Modules.HtmlText.Services
{
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
public interface IHtmlTextService
{
Task<List<Models.HtmlText>> GetHtmlTextsAsync(int moduleId);
Task<Models.HtmlText> GetHtmlTextAsync(int moduleId);
Task<Models.HtmlText> GetHtmlTextAsync(int htmlTextId, int moduleId);
Task<Models.HtmlText> AddHtmlTextAsync(Models.HtmlText htmltext);
Task DeleteHtmlTextAsync(int htmlTextId, int moduleId);
}
}

View File

@@ -16,6 +16,12 @@
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="versions" ResourceKey="Versions" ResourceType="@resourceType" HelpText="The number of content versions to preserve (note that zero means unlimited)">Versions: </Label>
<div class="col-sm-9">
<input id="versions" type="number" min="0" max="9" step="1" class="form-control" @bind="@_versions" />
</div>
</div>
</div>
</form>
@@ -26,12 +32,14 @@
private bool validated = false;
private string _dynamictokens;
private string _versions = "5";
protected override void OnInitialized()
{
try
{
_dynamictokens = SettingService.GetSetting(ModuleState.Settings, "DynamicTokens", "false");
_versions = SettingService.GetSetting(ModuleState.Settings, "Versions", "5");
}
catch (Exception ex)
{
@@ -45,6 +53,10 @@
{
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
settings = SettingService.SetSetting(settings, "DynamicTokens", _dynamictokens);
if (int.TryParse(_versions, out int versions) && versions >= 0 && versions <= 9)
{
settings = SettingService.SetSetting(settings, "Versions", versions.ToString());
}
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
}
catch (Exception ex)

View File

@@ -8,11 +8,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="10.0.2" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="10.0.2" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.2" />
<PackageReference Include="Radzen.Blazor" Version="8.6.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.3" />
<PackageReference Include="Radzen.Blazor" Version="9.0.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,174 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="CaseSensitive.Text" xml:space="preserve">
<value>Match Case?</value>
</data>
<data name="CaseSensitive.HelpText" xml:space="preserve">
<value>Specify if the replacement operation should be case sensitive</value>
</data>
<data name="Content.Text" xml:space="preserve">
<value>Module Content?</value>
</data>
<data name="Content.HelpText" xml:space="preserve">
<value>Specify if module content should be updated</value>
</data>
<data name="Pages.Text" xml:space="preserve">
<value>Page Properties?</value>
</data>
<data name="Pages.HelpText" xml:space="preserve">
<value>Specify if page properties should be updated (ie. name, title, headcontent, bodycontent)</value>
</data>
<data name="Site.Text" xml:space="preserve">
<value>Site Properties?</value>
</data>
<data name="Site.HelpText" xml:space="preserve">
<value>Specify if site properties should be updated (ie. name, headcontent, bodycontent)</value>
</data>
<data name="Replace.Text" xml:space="preserve">
<value>Replace With:</value>
</data>
<data name="Replace.HelpText" xml:space="preserve">
<value>Specify the replacement content</value>
</data>
<data name="Modules.Text" xml:space="preserve">
<value>Module Properties?</value>
</data>
<data name="Modules.HelpText" xml:space="preserve">
<value>Specify if module properties should be updated (ie. title, header, footer)</value>
</data>
<data name="Success.Save" xml:space="preserve">
<value>Your Global Replace Request Has Been Submitted And Will Be Executed Shortly. Please Be Patient.</value>
</data>
<data name="Error.Save" xml:space="preserve">
<value>Error Saving Global Replace</value>
</data>
<data name="Find.HelpText" xml:space="preserve">
<value>Specify the content which needs to be replaced</value>
</data>
<data name="Find.Text" xml:space="preserve">
<value>Find What:</value>
</data>
<data name="GlobalReplace.Header" xml:space="preserve">
<value>Global Replace</value>
</data>
<data name="GlobalReplace.Message" xml:space="preserve">
<value>This Operation is Permanent. Are You Sure You Wish To Proceed?</value>
</data>
</root>

View File

@@ -234,4 +234,10 @@
<data name="Pages.Heading" xml:space="preserve">
<value>Pages</value>
</data>
<data name="Fingerprint.Text" xml:space="preserve">
<value>Fingerprint:</value>
</data>
<data name="Fingerprint.HelpText" xml:space="preserve">
<value>A unique identifier for the module's static resources. This value can be changed by clicking the Save option below (ie. cache busting).</value>
</data>
</root>

View File

@@ -130,7 +130,7 @@
<value>Indicate if this module should be displayed on all pages</value>
</data>
<data name="Page.HelpText" xml:space="preserve">
<value>The page that the module is located on</value>
<value>The page that the module is located on. Please note that shared modules cannot be moved to other pages.</value>
</data>
<data name="Title.Text" xml:space="preserve">
<value>Title: </value>

View File

@@ -186,4 +186,10 @@
<data name="TimeZone.HelpText" xml:space="preserve">
<value>Your time zone</value>
</data>
<data name="CultureCode.Text" xml:space="preserve">
<value>Language:</value>
</data>
<data name="CultureCode.HelpText" xml:space="preserve">
<value>Your preferred language. Note that you will only be able to choose from languages supported on this site.</value>
</data>
</root>

View File

@@ -319,16 +319,16 @@
<value>The default alias for the site. Requests for non-default aliases will be redirected to the default alias.</value>
</data>
<data name="DefaultAlias.Text" xml:space="preserve">
<value>Default Alias: </value>
<value>Default?</value>
</data>
<data name="Aliases.Heading" xml:space="preserve">
<value>Site Urls</value>
</data>
<data name="AliasName" xml:space="preserve">
<value>Url</value>
<data name="AliasName.Text" xml:space="preserve">
<value>Url:</value>
</data>
<data name="AliasDefault" xml:space="preserve">
<value>Default?</value>
<data name="Default" xml:space="preserve">
<value>Default</value>
</data>
<data name="Confirm.Alias.Delete" xml:space="preserve">
<value>Are You Sure You Wish To Delete {0}?</value>
@@ -400,7 +400,7 @@
<value>Hybrid Enabled?</value>
</data>
<data name="Runtime.HelpText" xml:space="preserve">
<value>The render mode for UI components which require interactivity</value>
<value>The hosting model for UI components which require interactivity</value>
</data>
<data name="Runtime.Text" xml:space="preserve">
<value>Interactivity:</value>
@@ -495,4 +495,76 @@
<data name="StartTlsWhenAvailable" xml:space="preserve">
<value>Use TLS When Available</value>
</data>
<data name="AliasName.HelpText" xml:space="preserve">
<value>A url for this site. This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or virtual folders (ie. domain.com/folder).</value>
</data>
<data name="SiteGroupMembers.Text" xml:space="preserve">
<value>Group:</value>
</data>
<data name="SiteGroupMembers.HelpText" xml:space="preserve">
<value>The site groups in this tenant (database)</value>
</data>
<data name="Primary.Text" xml:space="preserve">
<value>Primary?</value>
</data>
<data name="Primary.HelpText" xml:space="preserve">
<value>Indicates if the selected member is the primary site of the site group</value>
</data>
<data name="GroupName.Text" xml:space="preserve">
<value>Name:</value>
</data>
<data name="GroupName.HelpText" xml:space="preserve">
<value>Name of the site group</value>
</data>
<data name="Primary" xml:space="preserve">
<value>Primary</value>
</data>
<data name="Secondary" xml:space="preserve">
<value>Secondary</value>
</data>
<data name="Compare" xml:space="preserve">
<value>Compare</value>
</data>
<data name="Update" xml:space="preserve">
<value>Update</value>
</data>
<data name="DeleteSiteGroupMember.Header" xml:space="preserve">
<value>Delete Site Group Member</value>
</data>
<data name="Confirm.SiteGroupMember.Delete" xml:space="preserve">
<value>Are You Sure You Wish To Delete This Member From The Site Group?</value>
</data>
<data name="Message.Required.GroupName" xml:space="preserve">
<value>Site Group Name Is Required</value>
</data>
<data name="Message.Site.Synchronize" xml:space="preserve">
<value>Site Submitted For Synchronization</value>
</data>
<data name="Site.Text" xml:space="preserve">
<value>Members:</value>
</data>
<data name="Site.HelpText" xml:space="preserve">
<value>The sites which are members of this site group</value>
</data>
<data name="Synchronized.Text" xml:space="preserve">
<value>Synchronized:</value>
</data>
<data name="GroupType.Text" xml:space="preserve">
<value>Type:</value>
</data>
<data name="GroupType.HelpText" xml:space="preserve">
<value>Defines the specific behavior of the site group</value>
</data>
<data name="Synchronized.HelpText" xml:space="preserve">
<value>The date/time when the site was last synchronized</value>
</data>
<data name="Synchronization" xml:space="preserve">
<value>Synchronization</value>
</data>
<data name="Localization" xml:space="preserve">
<value>Localization</value>
</data>
<data name="ChangeDetection" xml:space="preserve">
<value>Change Detection</value>
</data>
</root>

View File

@@ -123,29 +123,14 @@
<data name="SqlServer" xml:space="preserve">
<value>SQL Server</value>
</data>
<data name="Container.Select" xml:space="preserve">
<value>Select Container</value>
</data>
<data name="DefaultContainer.Text" xml:space="preserve">
<value>Default Container: </value>
</data>
<data name="Theme.Select" xml:space="preserve">
<value>Select Theme</value>
</data>
<data name="Aliases.HelpText" xml:space="preserve">
<value>The urls for the site (comman delimited). This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or a virtual folder (ie. domain.com/folder).</value>
</data>
<data name="DefaultContainer.HelpText" xml:space="preserve">
<value>Select the default container for the site</value>
<value>The primary url for the site. This can be a domain name (ie. domain.com), subdomain (ie. sub.domain.com) or a virtual folder (ie. domain.com/folder).</value>
</data>
<data name="Tenant.Text" xml:space="preserve">
<value>Database: </value>
</data>
<data name="Aliases.Text" xml:space="preserve">
<value>Urls: </value>
</data>
<data name="DefaultTheme.Text" xml:space="preserve">
<value>Default Theme: </value>
<value>Url: </value>
</data>
<data name="SiteTemplate.Select" xml:space="preserve">
<value>Select Site Template</value>
@@ -177,9 +162,6 @@
<data name="Name.HelpText" xml:space="preserve">
<value>Enter the name of the site</value>
</data>
<data name="DefaultTheme.HelpText" xml:space="preserve">
<value>Select the default theme for the site</value>
</data>
<data name="SiteTemplate.HelpText" xml:space="preserve">
<value>Select the site template</value>
</data>
@@ -222,12 +204,6 @@
<data name="Error.Database.LoadConfig" xml:space="preserve">
<value>Error loading Database Configuration Control</value>
</data>
<data name="RenderMode.HelpText" xml:space="preserve">
<value>The default render mode for the site</value>
</data>
<data name="RenderMode.Text" xml:space="preserve">
<value>Render Mode: </value>
</data>
<data name="ConnectionString.HelpText" xml:space="preserve">
<value>Enter a complete connection string including all parameters and delimiters</value>
</data>
@@ -240,10 +216,4 @@
<data name="EnterConnectionString" xml:space="preserve">
<value>Enter Connection String</value>
</data>
<data name="Runtime.HelpText" xml:space="preserve">
<value>The render mode for UI components which require interactivity</value>
</data>
<data name="Runtime.Text" xml:space="preserve">
<value>Interactivity:</value>
</data>
</root>

View File

@@ -186,4 +186,10 @@
<data name="Permissions.Heading" xml:space="preserve">
<value>Permissions</value>
</data>
<data name="Fingerprint.Text" xml:space="preserve">
<value>Fingerprint:</value>
</data>
<data name="Fingerprint.HelpText" xml:space="preserve">
<value>A unique identifier for the theme's static resources. This value can be changed by clicking the Save option below (ie. cache busting).</value>
</data>
</root>

View File

@@ -288,4 +288,10 @@
<data name="Error.Passkey.Fail" xml:space="preserve">
<value>Passkey Could Not Be Created</value>
</data>
<data name="CultureCode.Text" xml:space="preserve">
<value>Language:</value>
</data>
<data name="CultureCode.HelpText" xml:space="preserve">
<value>Your preferred language. Note that you will only be able to choose from languages supported on this site.</value>
</data>
</root>

View File

@@ -168,4 +168,10 @@
<data name="Confirmed.HelpText" xml:space="preserve">
<value>Indicates if the user's email is verified</value>
</data>
<data name="CultureCode.Text" xml:space="preserve">
<value>Language:</value>
</data>
<data name="CultureCode.HelpText" xml:space="preserve">
<value />
</data>
</root>

View File

@@ -252,4 +252,10 @@
<data name="Message.Logins.None" xml:space="preserve">
<value>You Do Not Have Any External Logins For This Site</value>
</data>
<data name="CultureCode.Text" xml:space="preserve">
<value>Language:</value>
</data>
<data name="CultureCode.HelpText" xml:space="preserve">
<value>The user's preferred language. Note that you will only be able to choose from languages supported on this site.</value>
</data>
</root>

View File

@@ -129,11 +129,8 @@
<data name="Import" xml:space="preserve">
<value>Import</value>
</data>
<data name="Message.Import.Failure" xml:space="preserve">
<value>User Import Failed. Please Review Your Event Log For More Detailed Information.</value>
</data>
<data name="Message.Import.Success" xml:space="preserve">
<value>User Import Successful. {0} Users Imported.</value>
<value>Your User Import Request Has Been Submitted And Will Be Executed Shortly. Please Be Patient.</value>
</data>
<data name="Message.Import.Validation" xml:space="preserve">
<value>You Must Specify A User File For Import</value>

View File

@@ -144,4 +144,10 @@
<data name="Message.File.Restricted" xml:space="preserve">
<value>Files With Extension Of {0} Are Restricted From Upload. Please Contact Your Administrator For More Information.</value>
</data>
<data name="File.MaxSize" xml:space="preserve">
<value>Maximum upload file size: {0} MB</value>
</data>
<data name="Message.File.TooLarge" xml:space="preserve">
<value>File(s) {0} exceed(s) the maximum upload size of {1} MB</value>
</data>
</root>

View File

@@ -123,4 +123,10 @@
<data name="DynamicTokens.Text" xml:space="preserve">
<value>Dynamic Tokens?</value>
</data>
<data name="Versions.HelpText" xml:space="preserve">
<value>The number of content versions to preserve (note that zero means unlimited)</value>
</data>
<data name="Versions.Text" xml:space="preserve">
<value>Versions:</value>
</data>
</root>

View File

@@ -204,6 +204,9 @@
<data name="System Update" xml:space="preserve">
<value>System Update</value>
</data>
<data name="Setting Management" xml:space="preserve">
<value>Setting Management</value>
</data>
<data name="Download" xml:space="preserve">
<value>Download</value>
</data>
@@ -480,4 +483,7 @@
<data name="Installed" xml:space="preserve">
<value>Installed</value>
</data>
<data name="Global Replace" xml:space="preserve">
<value>Global Replace</value>
</data>
</root>

View File

@@ -174,9 +174,6 @@
<data name="Title" xml:space="preserve">
<value>Title:</value>
</data>
<data name="System.Update" xml:space="preserve">
<value>Check For System Updates</value>
</data>
<data name="Visibility" xml:space="preserve">
<value>Visibility:</value>
</data>
@@ -201,4 +198,10 @@
<data name="Module.CopyExisting" xml:space="preserve">
<value>Copy Existing Module</value>
</data>
<data name="Synchronize" xml:space="preserve">
<value>Synchronize Site</value>
</data>
<data name="Copy" xml:space="preserve">
<value>Copy Page</value>
</data>
</root>

View File

@@ -14,8 +14,9 @@ namespace Oqtane.Services
/// Set the localization cookie
/// </summary>
/// <param name="culture"></param>
/// <param name="uiCulture"></param>
/// <returns></returns>
Task SetLocalizationCookieAsync(string culture);
Task SetLocalizationCookieAsync(string culture, string uiCulture);
}
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
@@ -23,7 +24,7 @@ namespace Oqtane.Services
{
public LocalizationCookieService(HttpClient http, SiteState siteState) : base(http, siteState) { }
public Task SetLocalizationCookieAsync(string culture)
public Task SetLocalizationCookieAsync(string culture, string uiCulture)
{
return Task.CompletedTask; // only used in server side rendering
}

View File

@@ -13,10 +13,16 @@ namespace Oqtane.Services
public interface ILocalizationService
{
/// <summary>
/// Returns a collection of supported cultures
/// Returns a collection of supported or installed cultures
/// </summary>
/// <returns></returns>
Task<IEnumerable<Culture>> GetCulturesAsync(bool installed);
/// <summary>
/// Returns a collection of neutral cultures
/// </summary>
/// <returns></returns>
Task<IEnumerable<Culture>> GetNeutralCulturesAsync();
}
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
@@ -26,6 +32,14 @@ namespace Oqtane.Services
private string Apiurl => CreateApiUrl("Localization");
public async Task<IEnumerable<Culture>> GetCulturesAsync(bool installed) => await GetJsonAsync<IEnumerable<Culture>>($"{Apiurl}?installed={installed}");
public async Task<IEnumerable<Culture>> GetCulturesAsync(bool installed)
{
return await GetJsonAsync<IEnumerable<Culture>>($"{Apiurl}?installed={installed}");
}
public async Task<IEnumerable<Culture>> GetNeutralCulturesAsync()
{
return await GetJsonAsync<IEnumerable<Culture>>($"{Apiurl}/neutral");
}
}
}

View File

@@ -71,6 +71,15 @@ namespace Oqtane.Services
/// <param name="pageId"></param>
/// <returns></returns>
Task DeletePageAsync(int pageId);
/// <summary>
/// Copies the modules from one page to another
/// </summary>
/// <param name="fromPageId"></param>
/// <param name="toPageId"></param>
/// <param name="usePagePermissions"></param>
/// <returns></returns>
Task CopyPageAsync(int fromPageId, int toPageId, bool usePagePermissions);
}
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
@@ -129,5 +138,10 @@ namespace Oqtane.Services
{
await DeleteAsync($"{Apiurl}/{pageId}");
}
public async Task CopyPageAsync(int fromPageId, int toPageId, bool usePagePermissions)
{
await PostAsync($"{Apiurl}/{fromPageId}/{toPageId}/{usePagePermissions}");
}
}
}

View File

@@ -0,0 +1,104 @@
using Oqtane.Models;
using System.Threading.Tasks;
using System.Net.Http;
using System.Collections.Generic;
using Oqtane.Documentation;
using Oqtane.Shared;
using System.Linq;
namespace Oqtane.Services
{
/// <summary>
/// Service to manage <see cref="Role"/>s on a <see cref="Site"/>
/// </summary>
public interface ISiteGroupMemberService
{
/// <summary>
/// Get all <see cref="SiteGroupMember"/>s
/// </summary>
/// <returns></returns>
Task<List<SiteGroupMember>> GetSiteGroupMembersAsync(int siteId, int siteGroupId);
/// <summary>
/// Get one specific <see cref="SiteGroupMember"/>
/// </summary>
/// <param name="siteGroupMemberId">ID-reference of a <see cref="SiteGroupMember"/></param>
/// <returns></returns>
Task<SiteGroupMember> GetSiteGroupMemberAsync(int siteGroupMemberId);
/// <summary>
/// Get one specific <see cref="SiteGroupMember"/>
/// </summary>
/// <param name="siteId">ID-reference of a <see cref="Site"/></param>
/// <param name="siteGroupId">ID-reference of a <see cref="SiteGroup"/></param>
/// <returns></returns>
Task<SiteGroupMember> GetSiteGroupMemberAsync(int siteId, int siteGroupId);
/// <summary>
/// Add / save a new <see cref="SiteGroupMember"/> to the database.
/// </summary>
/// <param name="siteGroupMember"></param>
/// <returns></returns>
Task<SiteGroupMember> AddSiteGroupMemberAsync(SiteGroupMember siteGroupMember);
/// <summary>
/// Update a <see cref="SiteGroupMember"/> in the database.
/// </summary>
/// <param name="siteGroupMember"></param>
/// <returns></returns>
Task<SiteGroupMember> UpdateSiteGroupMemberAsync(SiteGroupMember siteGroupMember);
/// <summary>
/// Delete a <see cref="SiteGroupMember"/> in the database.
/// </summary>
/// <param name="siteGroupMemberId">ID-reference of a <see cref="SiteGroupMember"/></param>
/// <returns></returns>
Task DeleteSiteGroupMemberAsync(int siteGroupMemberId);
}
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
public class SiteGroupMemberService : ServiceBase, ISiteGroupMemberService
{
public SiteGroupMemberService(HttpClient http, SiteState siteState) : base(http, siteState) { }
private string Apiurl => CreateApiUrl("SiteGroupMember");
public async Task<List<SiteGroupMember>> GetSiteGroupMembersAsync(int siteId, int siteGroupId)
{
return await GetJsonAsync<List<SiteGroupMember>>($"{Apiurl}?siteid={siteId}&groupid={siteGroupId}", Enumerable.Empty<SiteGroupMember>().ToList());
}
public async Task<SiteGroupMember> GetSiteGroupMemberAsync(int siteGroupMemberId)
{
return await GetJsonAsync<SiteGroupMember>($"{Apiurl}/{siteGroupMemberId}");
}
public async Task<SiteGroupMember> GetSiteGroupMemberAsync(int siteId, int siteGroupId)
{
var siteGroupMembers = await GetSiteGroupMembersAsync(siteId, siteGroupId);
if (siteGroupMembers != null && siteGroupMembers.Count > 0)
{
return siteGroupMembers[0];
}
else
{
return null;
}
}
public async Task<SiteGroupMember> AddSiteGroupMemberAsync(SiteGroupMember siteGroupMember)
{
return await PostJsonAsync<SiteGroupMember>(Apiurl, siteGroupMember);
}
public async Task<SiteGroupMember> UpdateSiteGroupMemberAsync(SiteGroupMember siteGroupMember)
{
return await PutJsonAsync<SiteGroupMember>($"{Apiurl}/{siteGroupMember.SiteGroupId}", siteGroupMember);
}
public async Task DeleteSiteGroupMemberAsync(int siteGroupMemberId)
{
await DeleteAsync($"{Apiurl}/{siteGroupMemberId}");
}
}
}

View File

@@ -0,0 +1,94 @@
using Oqtane.Models;
using System.Threading.Tasks;
using System.Net.Http;
using System.Collections.Generic;
using Oqtane.Documentation;
using Oqtane.Shared;
using System.Linq;
namespace Oqtane.Services
{
/// <summary>
/// Service to manage <see cref="Role"/>s on a <see cref="Site"/>
/// </summary>
public interface ISiteGroupService
{
/// <summary>
/// Get all <see cref="SiteGroup"/>s
/// </summary>
/// <returns></returns>
Task<List<SiteGroup>> GetSiteGroupsAsync();
/// <summary>
/// Get all <see cref="SiteGroup"/>s
/// </summary>
/// <returns></returns>
Task<List<SiteGroup>> GetSiteGroupsAsync(int primarySiteId);
/// <summary>
/// Get one specific <see cref="SiteGroup"/>
/// </summary>
/// <param name="siteGroupId">ID-reference of a <see cref="SiteGroup"/></param>
/// <returns></returns>
Task<SiteGroup> GetSiteGroupAsync(int siteGroupId);
/// <summary>
/// Add / save a new <see cref="SiteGroup"/> to the database.
/// </summary>
/// <param name="group"></param>
/// <returns></returns>
Task<SiteGroup> AddSiteGroupAsync(SiteGroup siteGroup);
/// <summary>
/// Update a <see cref="SiteGroup"/> in the database.
/// </summary>
/// <param name="group"></param>
/// <returns></returns>
Task<SiteGroup> UpdateSiteGroupAsync(SiteGroup siteGroup);
/// <summary>
/// Delete a <see cref="SiteGroup"/> in the database.
/// </summary>
/// <param name="siteGroupId">ID-reference of a <see cref="SiteGroup"/></param>
/// <returns></returns>
Task DeleteSiteGroupAsync(int siteGroupId);
}
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
public class SiteGroupService : ServiceBase, ISiteGroupService
{
public SiteGroupService(HttpClient http, SiteState siteState) : base(http, siteState) { }
private string Apiurl => CreateApiUrl("SiteGroup");
public async Task<List<SiteGroup>> GetSiteGroupsAsync()
{
return await GetSiteGroupsAsync(-1);
}
public async Task<List<SiteGroup>> GetSiteGroupsAsync(int primarySiteId)
{
return await GetJsonAsync<List<SiteGroup>>($"{Apiurl}?siteid={primarySiteId}", Enumerable.Empty<SiteGroup>().ToList());
}
public async Task<SiteGroup> GetSiteGroupAsync(int siteGroupId)
{
return await GetJsonAsync<SiteGroup>($"{Apiurl}/{siteGroupId}");
}
public async Task<SiteGroup> AddSiteGroupAsync(SiteGroup siteGroup)
{
return await PostJsonAsync<SiteGroup>(Apiurl, siteGroup);
}
public async Task<SiteGroup> UpdateSiteGroupAsync(SiteGroup siteGroup)
{
return await PutJsonAsync<SiteGroup>($"{Apiurl}/{siteGroup.SiteGroupId}", siteGroup);
}
public async Task DeleteSiteGroupAsync(int siteGroupId)
{
await DeleteAsync($"{Apiurl}/{siteGroupId}");
}
}
}

View File

@@ -0,0 +1,46 @@
using Oqtane.Models;
using System.Threading.Tasks;
using System.Net.Http;
using Oqtane.Documentation;
using Oqtane.Shared;
namespace Oqtane.Services
{
/// <summary>
/// Service to manage tasks (<see cref="SiteTask"/>)
/// </summary>
public interface ISiteTaskService
{
/// <summary>
/// Return a specific task
/// </summary>
/// <param name="siteTaskId"></param>
/// <returns></returns>
Task<SiteTask> GetSiteTaskAsync(int siteTaskId);
/// <summary>
/// Adds a new task
/// </summary>
/// <param name="siteTask"></param>
/// <returns></returns>
Task<SiteTask> AddSiteTaskAsync(SiteTask siteTask);
}
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
public class SiteTaskService : ServiceBase, ISiteTaskService
{
public SiteTaskService(HttpClient http, SiteState siteState) : base(http, siteState) { }
private string Apiurl => CreateApiUrl("SiteTask");
public async Task<SiteTask> GetSiteTaskAsync(int siteTaskId)
{
return await GetJsonAsync<SiteTask>($"{Apiurl}/{siteTaskId}");
}
public async Task<SiteTask> AddSiteTaskAsync(SiteTask siteTask)
{
return await PostJsonAsync<SiteTask>(Apiurl, siteTask);
}
}
}

View File

@@ -161,15 +161,6 @@ namespace Oqtane.Services
/// <returns></returns>
Task<string> GetPasswordRequirementsAsync(int siteId);
/// <summary>
/// Bulk import of users
/// </summary>
/// <param name="siteId">ID of a <see cref="Site"/></param>
/// <param name="fileId">ID of a <see cref="File"/></param>
/// <param name="notify">Indicates if new users should be notified by email</param>
/// <returns></returns>
Task<Dictionary<string, string>> ImportUsersAsync(int siteId, int fileId, bool notify);
/// <summary>
/// Get passkeys for a user
/// </summary>
@@ -351,11 +342,6 @@ namespace Oqtane.Services
return string.Format(passwordValidationCriteriaTemplate, minimumlength, uniquecharacters, digitRequirement, uppercaseRequirement, lowercaseRequirement, punctuationRequirement);
}
public async Task<Dictionary<string, string>> ImportUsersAsync(int siteId, int fileId, bool notify)
{
return await PostJsonAsync<Dictionary<string, string>>($"{Apiurl}/import?siteid={siteId}&fileid={fileId}&notify={notify}", null);
}
public async Task<List<UserPasskey>> GetPasskeysAsync(int userId)
{
return await GetJsonAsync<List<UserPasskey>>($"{Apiurl}/passkey?id={userId}");

View File

@@ -97,6 +97,7 @@
Alias = PageState.Alias,
Site = new Site
{
SiteId = PageState.Site.SiteId,
DefaultContainerType = PageState.Site.DefaultContainerType,
Settings = PageState.Site.Settings,
Themes = PageState.Site.Themes

View File

@@ -11,6 +11,7 @@
@inject ILogService logger
@inject ISettingService SettingService
@inject IJSRuntime jsRuntime
@inject ISiteGroupService SiteGroupService
@inject IServiceProvider ServiceProvider
@inject ILogService LoggingService
@inject IStringLocalizer<ControlPanelInteractive> Localizer
@@ -34,6 +35,11 @@
<button type="button" data-bs-dismiss="offcanvas" class="btn btn-primary col-12" @onclick=@(async () => Navigate("Admin"))>@Localizer["AdminDash"]</button>
</div>
</div>
@if (_siteGroups.Any(item => (item.Type == SiteGroupTypes.Synchronization || item.Type == SiteGroupTypes.ChangeDetection) && item.PrimarySiteId == PageState.Site.SiteId))
{
<hr class="app-rule" />
<button type="button" class="btn btn-secondary col-12 mt-1" @onclick="SynchronizeSite">@Localizer["Synchronize"]</button>
}
<hr class="app-rule" />
}
@if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList))
@@ -53,18 +59,29 @@
<button type="button" class="btn btn-danger col ms-1" @onclick="ConfirmDelete">@SharedLocalizer["Delete"]</button>
</div>
</div>
<div class="row d-flex">
<div class="col">
@if (UserSecurity.ContainsRole(PageState.Page.PermissionList, PermissionNames.View, RoleNames.Everyone))
{
<button type="button" class="btn btn-secondary col-12" @onclick=@(async () => Publish("unpublish"))>@Localizer["Page.Unpublish"]</button>
}
else
{
<button type="button" class="btn btn-secondary col-12" @onclick=@(async () => Publish("publish"))>@Localizer["Page.Publish"]</button>
}
@if (PageState.Page.UserId == null)
{
<div class="row d-flex mb-2">
<div class="col">
<button type="button" class="btn btn-secondary col-12" data-bs-dismiss="offcanvas" @onclick=@(async () => Navigate("Copy"))>@Localizer["Copy"]</button>
</div>
</div>
</div>
}
@if (!PageState.Page.Path.StartsWith("admin/"))
{
<div class="row d-flex">
<div class="col">
@if (UserSecurity.ContainsRole(PageState.Page.PermissionList, PermissionNames.View, RoleNames.Everyone))
{
<button type="button" class="btn btn-secondary col-12" @onclick=@(async () => Publish("unpublish"))>@Localizer["Page.Unpublish"]</button>
}
else
{
<button type="button" class="btn btn-secondary col-12" @onclick=@(async () => Publish("publish"))>@Localizer["Page.Publish"]</button>
}
</div>
</div>
}
<hr class="app-rule" />
@if (_deleteConfirmation)
@@ -149,7 +166,7 @@
<option value="@p.PageId">@p.Name</option>
}
</select>
<select class="form-select mt-1" @bind="@_moduleId">
<select class="form-select mt-1" value="@_moduleId" @onchange="(e => PageModuleChanged(e))">
<option value="-">&lt;@Localizer["Module.Select"]&gt;</option>
@foreach (Module module in _modules)
{
@@ -257,6 +274,7 @@
private List<Page> _pages = new List<Page>();
private List<Module> _modules = new List<Module>();
private List<ThemeControl> _containers = new List<ThemeControl>();
private List<SiteGroup> _siteGroups = new List<SiteGroup>();
private string _category = "Common";
private string _pane = "";
@@ -287,6 +305,7 @@
_allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Page.SiteId);
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(_category)).ToList();
_categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',', StringSplitOptions.RemoveEmptyEntries)).Distinct().Where(item => item != "Headless").ToList();
_siteGroups = await SiteGroupService.GetSiteGroupsAsync(PageState.Site.SiteId);
}
}
@@ -339,6 +358,20 @@
StateHasChanged();
}
private async Task PageModuleChanged(ChangeEventArgs e)
{
_moduleId = (string)e.Value;
if (_moduleId != "-")
{
_title = _modules.First(item => item.ModuleId == int.Parse(_moduleId)).Title;
}
else
{
_title = "";
}
StateHasChanged();
}
private async Task AddModule()
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList))
@@ -488,7 +521,12 @@
case "Edit":
// get page management moduleid
moduleId = int.Parse(PageState.Site.Settings[Constants.PageManagementModule]);
NavigationManager.NavigateTo(Utilities.EditUrl(PageState.Alias.Path, "admin/pages", moduleId, location, $"id={PageState.Page.PageId}&returnurl={WebUtility.UrlEncode(PageState.Route.PathAndQuery)}"));
NavigationManager.NavigateTo(Utilities.EditUrl(PageState.Alias.Path, "admin/pages", moduleId, "Edit", $"id={PageState.Page.PageId}&returnurl={WebUtility.UrlEncode(PageState.Route.PathAndQuery)}"));
break;
case "Copy":
// get page management moduleid
moduleId = int.Parse(PageState.Site.Settings[Constants.PageManagementModule]);
NavigationManager.NavigateTo(Utilities.EditUrl(PageState.Alias.Path, "admin/pages", moduleId, "Edit", $"id={PageState.Page.PageId}&copy=true&returnurl={WebUtility.UrlEncode(PageState.Route.PathAndQuery)}"));
break;
}
}
@@ -631,4 +669,14 @@
{
_message = "";
}
private async Task SynchronizeSite()
{
foreach (var group in _siteGroups.Where(item => (item.Type == SiteGroupTypes.Synchronization || item.Type == SiteGroupTypes.ChangeDetection) && item.PrimarySiteId == PageState.Site.SiteId))
{
group.Synchronize = true;
await SiteGroupService.UpdateSiteGroupAsync(group);
}
NavigationManager.NavigateTo(Utilities.NavigateUrl(PageState.Alias.Path, PageState.Page.Path, ""), true);
}
}

View File

@@ -6,22 +6,29 @@
@inject ILocalizationCookieService LocalizationCookieService
@inject NavigationManager NavigationManager
@if (_supportedCultures?.Count() > 1)
@if (PageState.Site.Languages.Count() > 1)
{
<div class="app-languages btn-group pe-1" role="group">
<button id="btnCultures" type="button" class="btn @ButtonClass dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="oi oi-globe"></span>
</button>
<div class="dropdown-menu @MenuAlignment" aria-labelledby="btnCultures">
@foreach (var culture in _supportedCultures)
@foreach (var language in PageState.Site.Languages)
{
@if (PageState.RenderMode == RenderModes.Interactive)
@if (_contentLocalization)
{
<a class="dropdown-item @(CultureInfo.CurrentUICulture.Name == culture.Name ? "active" : String.Empty)" href="#" @onclick="@(async e => await SetCultureAsync(culture.Name))" @onclick:preventDefault="true">@culture.DisplayName</a>
<a class="dropdown-item @(PageState.Site.CultureCode == language.Code ? "active" : String.Empty)" href="@(PageState.Alias.Protocol + language.AliasName)" data-enhance-nav="false">@language.Name</a>
}
else
{
<a class="dropdown-item @(CultureInfo.CurrentUICulture.Name == culture.Name ? "active" : String.Empty)" href="@NavigateUrl(PageState.Page.Path, "culture=" + culture.Name)" data-enhance-nav="false">@culture.DisplayName</a>
@if (PageState.RenderMode == RenderModes.Interactive)
{
<a class="dropdown-item @(CultureInfo.CurrentUICulture.Name == language.Code ? "active" : String.Empty)" href="#" @onclick="@(async e => await SetCultureAsync(language.Code))" @onclick:preventDefault="true">@language.Name</a>
}
else
{
<a class="dropdown-item @(CultureInfo.CurrentUICulture.Name == language.Code ? "active" : String.Empty)" href="@NavigateUrl(PageState.Page.Path, "culture=" + language.Code)" data-enhance-nav="false">@language.Name</a>
}
}
}
</div>
@@ -29,7 +36,7 @@
}
@code{
private IEnumerable<Culture> _supportedCultures;
private bool _contentLocalization;
private string MenuAlignment = string.Empty;
[Parameter]
@@ -41,14 +48,15 @@
{
MenuAlignment = DropdownAlignment.ToLower() == "right" ? "dropdown-menu-end" : string.Empty;
_supportedCultures = PageState.Languages.Select(l => new Culture { Name = l.Code, DisplayName = l.Name });
// if AliasName is populated it means the site is using content localization
_contentLocalization = PageState.Languages.Any(item => !string.IsNullOrEmpty(item.AliasName));
if (PageState.QueryString.ContainsKey("culture"))
{
var culture = PageState.QueryString["culture"];
if (_supportedCultures.Any(item => item.Name == culture))
if (PageState.Site.Languages.Any(item => item.Code == culture))
{
await LocalizationCookieService.SetLocalizationCookieAsync(culture);
await LocalizationCookieService.SetLocalizationCookieAsync(PageState.Site.CultureCode, culture);
}
NavigationManager.NavigateTo(NavigationManager.Uri.Replace($"?culture={culture}", ""));
}
@@ -58,7 +66,7 @@
{
if (culture != CultureInfo.CurrentUICulture.Name)
{
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(PageState.Site.CultureCode, culture));
var interop = new Interop(JSRuntime);
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360, true, "Lax");
NavigationManager.NavigateTo(NavigationManager.Uri, true);

View File

@@ -233,15 +233,8 @@
if (page == null && route.PagePath == "") // naked path refers to site home page
{
if (site.HomePageId != null)
{
page = site.Pages.FirstOrDefault(item => item.PageId == site.HomePageId);
}
if (page == null)
{
// fallback to use the first page in the collection
page = site.Pages.FirstOrDefault();
}
// fallback to use the first page in the collection
page = site.Pages.FirstOrDefault();
}
if (page == null)
{
@@ -632,11 +625,11 @@
{
if (resource.ResourceType == ResourceType.Stylesheet || resource.Level != ResourceLevel.Site)
{
if (resource.Url.StartsWith("~"))
if (!string.IsNullOrEmpty(resource.Url) && resource.Url.StartsWith("~"))
{
resource.Url = resource.Url.Replace("~", "/" + type + "/" + name + "/").Replace("//", "/");
}
if (!resource.Url.Contains("://") && alias.BaseUrl != "" && !resource.Url.StartsWith(alias.BaseUrl))
if (!string.IsNullOrEmpty(resource.Url) && !resource.Url.Contains("://") && alias.BaseUrl != "" && !resource.Url.StartsWith(alias.BaseUrl))
{
resource.Url = alias.BaseUrl + resource.Url;
}

View File

@@ -227,38 +227,4 @@
}
return stylesheets;
}
private string ManageScripts(List<Resource> resources, Alias alias)
{
var scripts = "";
if (resources != null)
{
foreach (var resource in resources.Where(item => item.ResourceType == ResourceType.Script && item.Location == ResourceLocation.Head))
{
var script = CreateScript(resource, alias);
if (!scripts.Contains(script, StringComparison.OrdinalIgnoreCase))
{
scripts += script + Environment.NewLine;
}
}
}
return scripts;
}
private string CreateScript(Resource resource, Alias alias)
{
if (!string.IsNullOrEmpty(resource.Url))
{
var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url;
return "<script src=\"" + url + "\"" +
((!string.IsNullOrEmpty(resource.Integrity)) ? " integrity=\"" + resource.Integrity + "\"" : "") +
((!string.IsNullOrEmpty(resource.CrossOrigin)) ? " crossorigin=\"" + resource.CrossOrigin + "\"" : "") +
"></script>";
}
else
{
// inline script
return "<script>" + resource.Content + "</script>";
}
}
}

View File

@@ -11,6 +11,7 @@
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.Extensions.Localization
@using Microsoft.JSInterop
@using Microsoft.AspNetCore.Authorization
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Oqtane.Client

View File

@@ -18,7 +18,7 @@
<ApplicationId>com.oqtane.maui</ApplicationId>
<!-- Versions -->
<ApplicationDisplayVersion>10.0.4</ApplicationDisplayVersion>
<ApplicationDisplayVersion>10.1.0</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion>
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
@@ -54,11 +54,11 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.2" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.2" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="10.0.2" />
<PackageReference Include="System.Net.Http.Json" Version="10.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="10.0.3" />
<PackageReference Include="System.Net.Http.Json" Version="10.0.3" />
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="$(MauiVersion)" />

View File

@@ -2,7 +2,7 @@
<package>
<metadata>
<id>Oqtane.Client</id>
<version>10.0.4</version>
<version>10.1.0</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@@ -12,18 +12,18 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.4</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.1.0</releaseNotes>
<readme>readme.md</readme>
<icon>icon.png</icon>
<tags>oqtane</tags>
<dependencies>
<group targetFramework="net10.0">
<dependency id="Oqtane.Shared" version="10.0.4" exclude="Build,Analyzers" />
<dependency id="Microsoft.AspNetCore.Components.WebAssembly" version="10.0.2" exclude="Build,Analyzers" />
<dependency id="Microsoft.AspNetCore.Components.WebAssembly.Authentication" version="10.0.2" exclude="Build,Analyzers" />
<dependency id="Microsoft.Extensions.Localization" version="10.0.2" exclude="Build,Analyzers" />
<dependency id="Microsoft.Extensions.Http" version="10.0.2" exclude="Build,Analyzers" />
<dependency id="Radzen.Blazor" version="8.6.0" exclude="Build,Analyzers" />
<dependency id="Oqtane.Shared" version="10.1.0" exclude="Build,Analyzers" />
<dependency id="Microsoft.AspNetCore.Components.WebAssembly" version="10.0.3" exclude="Build,Analyzers" />
<dependency id="Microsoft.AspNetCore.Components.WebAssembly.Authentication" version="10.0.3" exclude="Build,Analyzers" />
<dependency id="Microsoft.Extensions.Localization" version="10.0.3" exclude="Build,Analyzers" />
<dependency id="Microsoft.Extensions.Http" version="10.0.3" exclude="Build,Analyzers" />
<dependency id="Radzen.Blazor" version="9.0.0" exclude="Build,Analyzers" />
</group>
</dependencies>
</metadata>

View File

@@ -2,7 +2,7 @@
<package>
<metadata>
<id>Oqtane.Framework</id>
<version>10.0.4</version>
<version>10.1.0</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@@ -11,8 +11,8 @@
<copyright>.NET Foundation</copyright>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v10.0.4/Oqtane.Framework.10.0.4.Upgrade.zip</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.4</releaseNotes>
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v10.1.0/Oqtane.Framework.10.1.0.Upgrade.zip</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.1.0</releaseNotes>
<readme>readme.md</readme>
<icon>icon.png</icon>
<tags>oqtane framework</tags>

View File

@@ -2,7 +2,7 @@
<package>
<metadata>
<id>Oqtane.Server</id>
<version>10.0.4</version>
<version>10.1.0</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@@ -12,29 +12,29 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.4</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.1.0</releaseNotes>
<readme>readme.md</readme>
<icon>icon.png</icon>
<tags>oqtane</tags>
<dependencies>
<group targetFramework="net10.0">
<dependency id="Oqtane.Client" version="10.0.4" exclude="Build,Analyzers" />
<dependency id="Oqtane.Shared" version="10.0.4" exclude="Build,Analyzers" />
<dependency id="Microsoft.AspNetCore.Components.WebAssembly.Server" version="10.0.2" exclude="Build,Analyzers" />
<dependency id="Microsoft.AspNetCore.Identity.EntityFrameworkCore" version="10.0.2" exclude="Build,Analyzers" />
<dependency id="Microsoft.AspNetCore.Authentication.OpenIdConnect" version="10.0.2" exclude="Build,Analyzers" />
<dependency id="Microsoft.EntityFrameworkCore.Relational" version="10.0.2" exclude="Build,Analyzers" />
<dependency id="Oqtane.Client" version="10.1.0" exclude="Build,Analyzers" />
<dependency id="Oqtane.Shared" version="10.1.0" exclude="Build,Analyzers" />
<dependency id="Microsoft.AspNetCore.Components.WebAssembly.Server" version="10.0.3" exclude="Build,Analyzers" />
<dependency id="Microsoft.AspNetCore.Identity.EntityFrameworkCore" version="10.0.3" exclude="Build,Analyzers" />
<dependency id="Microsoft.AspNetCore.Authentication.OpenIdConnect" version="10.0.3" exclude="Build,Analyzers" />
<dependency id="Microsoft.EntityFrameworkCore.Relational" version="10.0.3" exclude="Build,Analyzers" />
<dependency id="SixLabors.ImageSharp" version="3.1.12" exclude="Build,Analyzers" />
<dependency id="HtmlAgilityPack" version="1.12.4" exclude="Build,Analyzers" />
<dependency id="Swashbuckle.AspNetCore" version="10.1.0" exclude="Build,Analyzers" />
<dependency id="Swashbuckle.AspNetCore" version="10.1.2" exclude="Build,Analyzers" />
<dependency id="MailKit" version="4.14.1" exclude="Build,Analyzers" />
<dependency id="MySql.Data" version="9.5.0" exclude="Build,Analyzers" />
<dependency id="MySql.EntityFrameworkCore" version="10.0.0-rc" exclude="Build,Analyzers" />
<dependency id="EFCore.NamingConventions" version="10.0.0" exclude="Build,Analyzers" />
<dependency id="MySql.Data" version="9.6.0" exclude="Build,Analyzers" />
<dependency id="MySql.EntityFrameworkCore" version="10.0.1" exclude="Build,Analyzers" />
<dependency id="EFCore.NamingConventions" version="10.0.1" exclude="Build,Analyzers" />
<dependency id="Npgsql.EntityFrameworkCore.PostgreSQL" version="10.0.0" exclude="Build,Analyzers" />
<dependency id="Microsoft.EntityFrameworkCore.Sqlite" version="10.0.2" exclude="Build,Analyzers" />
<dependency id="Microsoft.Data.Sqlite.Core" version="10.0.2" exclude="Build,Analyzers" />
<dependency id="Microsoft.EntityFrameworkCore.SqlServer" version="10.0.2" exclude="Build,Analyzers" />
<dependency id="Microsoft.EntityFrameworkCore.Sqlite" version="10.0.3" exclude="Build,Analyzers" />
<dependency id="Microsoft.Data.Sqlite.Core" version="10.0.3" exclude="Build,Analyzers" />
<dependency id="Microsoft.EntityFrameworkCore.SqlServer" version="10.0.3" exclude="Build,Analyzers" />
</group>
</dependencies>
<frameworkReferences>

View File

@@ -2,7 +2,7 @@
<package>
<metadata>
<id>Oqtane.Shared</id>
<version>10.0.4</version>
<version>10.1.0</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@@ -12,14 +12,14 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.4</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.1.0</releaseNotes>
<readme>readme.md</readme>
<icon>icon.png</icon>
<tags>oqtane</tags>
<dependencies>
<group targetFramework="net10.0">
<dependency id="Microsoft.EntityFrameworkCore" version="10.0.2" exclude="Build,Analyzers" />
<dependency id="Microsoft.Extensions.DependencyInjection.Abstractions" version="10.0.2" exclude="Build,Analyzers" />
<dependency id="Microsoft.EntityFrameworkCore" version="10.0.3" exclude="Build,Analyzers" />
<dependency id="Microsoft.Extensions.DependencyInjection.Abstractions" version="10.0.3" exclude="Build,Analyzers" />
<dependency id="NodaTime" version="3.3.0" exclude="Build,Analyzers" />
</group>
</dependencies>

View File

@@ -2,7 +2,7 @@
<package>
<metadata>
<id>Oqtane.Updater</id>
<version>10.0.4</version>
<version>10.1.0</version>
<authors>Shaun Walker</authors>
<owners>.NET Foundation</owners>
<title>Oqtane Framework</title>
@@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.4</releaseNotes>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.1.0</releaseNotes>
<readme>readme.md</readme>
<icon>icon.png</icon>
<tags>oqtane</tags>

View File

@@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net10.0\publish\*" -DestinationPath "Oqtane.Framework.10.0.4.Install.zip" -Force
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net10.0\publish\*" -DestinationPath "Oqtane.Framework.10.1.0.Install.zip" -Force

View File

@@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net10.0\publish\*" -DestinationPath "Oqtane.Framework.10.0.4.Upgrade.zip" -Force
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net10.0\publish\*" -DestinationPath "Oqtane.Framework.10.1.0.Upgrade.zip" -Force

View File

@@ -79,6 +79,7 @@
@((MarkupString)_scripts)
@((MarkupString)_bodyResources)
@((MarkupString)_bodyContent)
@if (_renderMode == RenderModes.Static)
{
<page-script src="./js/reload.js?v=@_fingerprint"></page-script>
@@ -107,6 +108,7 @@
private string _language = "en";
private string _headResources = "";
private string _bodyResources = "";
private string _bodyContent = "";
private string _styleSheets = "";
private string _scripts = "";
private string _message = "";
@@ -157,15 +159,8 @@
var page = site.Pages.FirstOrDefault(item => item.Path.Equals(route.PagePath, StringComparison.OrdinalIgnoreCase));
if (page == null && route.PagePath == "") // naked path refers to site home page
{
if (site.HomePageId != null)
{
page = site.Pages.FirstOrDefault(item => item.PageId == site.HomePageId);
}
if (page == null)
{
// fallback to use the first page in the collection
page = site.Pages.FirstOrDefault();
}
// fallback to use the first page in the collection
page = site.Pages.FirstOrDefault();
}
if (page == null)
{
@@ -193,43 +188,20 @@
await GetJwtToken(alias);
}
// includes resources
// include resources
var resources = await GetPageResources(alias, site, page, modules, int.Parse(route.ModuleId, CultureInfo.InvariantCulture), route.Action);
ManageStyleSheets(resources);
ManageScripts(resources, alias);
AddBodyContent(site.BodyContent);
AddBodyContent(page.BodyContent);
ManageLocalization(site);
// generate scripts
// PWA script
if (site.PwaIsEnabled && site.PwaAppIconFileId != null && site.PwaSplashIconFileId != null)
{
_scripts += CreatePWAScript(alias, site, route);
}
// set culture if not specified
string cultureCookie = Context.Request.Cookies[Shared.CookieRequestCultureProvider.DefaultCookieName];
if (cultureCookie == null)
{
// get default language for site
if (site.Languages.Any())
{
// use default language if specified otherwise use first language in collection
cultureCookie = (site.Languages.Where(l => l.IsDefault).SingleOrDefault() ?? site.Languages.First()).Code;
}
else
{
// fallback language
cultureCookie = LocalizationManager.GetDefaultCulture();
}
// convert language code to culture cookie format (ie. "c=en|uic=en")
cultureCookie = Shared.CookieRequestCultureProvider.MakeCookieValue(new Models.RequestCulture(cultureCookie));
SetLocalizationCookie(cultureCookie);
}
// set language for page
if (!string.IsNullOrEmpty(cultureCookie))
{
_language = Shared.CookieRequestCultureProvider.ParseCookieValue(cultureCookie).Culture.Name;
}
// create initial PageState
_pageState = new PageState
{
@@ -550,8 +522,6 @@
}
else
{
var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url;
var dataAttributes = "";
if (!resource.DataAttributes.ContainsKey("data-reload"))
{
@@ -573,12 +543,24 @@
}
}
return "<script src=\"" + url + "\"" +
((!string.IsNullOrEmpty(resource.Type)) ? " type=\"" + resource.Type + "\"" : "") +
((!string.IsNullOrEmpty(resource.Integrity)) ? " integrity=\"" + resource.Integrity + "\"" : "") +
((!string.IsNullOrEmpty(resource.CrossOrigin)) ? " crossorigin=\"" + resource.CrossOrigin + "\"" : "") +
((!string.IsNullOrEmpty(dataAttributes)) ? dataAttributes : "") +
"></script>";
if (!string.IsNullOrEmpty(resource.Url))
{
var url = (string.IsNullOrEmpty(resource.Url) || resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url;
return "<script src=\"" + url + "\"" +
((!string.IsNullOrEmpty(resource.Type)) ? " type=\"" + resource.Type + "\"" : "") +
((!string.IsNullOrEmpty(resource.Integrity)) ? " integrity=\"" + resource.Integrity + "\"" : "") +
((!string.IsNullOrEmpty(resource.CrossOrigin)) ? " crossorigin=\"" + resource.CrossOrigin + "\"" : "") +
((!string.IsNullOrEmpty(dataAttributes)) ? dataAttributes : "") +
"></script>";
}
else
{
return "<script" +
((!string.IsNullOrEmpty(resource.Type)) ? " type=\"" + resource.Type + "\"" : "") +
((!string.IsNullOrEmpty(dataAttributes)) ? dataAttributes : "") +
">" + resource.Content + "</script>";
}
}
}
@@ -751,11 +733,11 @@
{
if (rendermode == RenderModes.Static || resource.ResourceType == ResourceType.Stylesheet || resource.Level == ResourceLevel.Site)
{
if (resource.Url.StartsWith("~"))
if (!string.IsNullOrEmpty(resource.Url) && resource.Url.StartsWith("~"))
{
resource.Url = resource.Url.Replace("~", "/" + type + "/" + name + "/").Replace("//", "/");
}
if (!resource.Url.Contains("://") && alias.BaseUrl != "" && !resource.Url.StartsWith(alias.BaseUrl))
if (!string.IsNullOrEmpty(resource.Url) && !resource.Url.Contains("://") && alias.BaseUrl != "" && !resource.Url.StartsWith(alias.BaseUrl))
{
resource.Url = alias.BaseUrl + resource.Url;
}
@@ -803,4 +785,71 @@
}
}
}
private void AddBodyContent(string content)
{
if (!string.IsNullOrEmpty(content))
{
var elements = content.Split('<', StringSplitOptions.RemoveEmptyEntries);
foreach (var element in elements)
{
if (_renderMode == RenderModes.Static || (!element.ToLower().StartsWith("script") && !element.ToLower().StartsWith("/script")))
{
if (!_bodyContent.Contains("<" + element) || element.StartsWith("/"))
{
_bodyContent += "<" + element;
}
}
}
_bodyContent += "\n";
}
}
private void ManageLocalization(Site site)
{
// get user culture (ui localization)
var uiCulture = site?.User?.CultureCode;
if (string.IsNullOrEmpty(uiCulture))
{
// get default language for site
if (site.Languages.Any())
{
// use default language if specified otherwise use first language in collection
uiCulture = (site.Languages.Where(l => l.IsDefault).SingleOrDefault() ?? site.Languages.First()).Code;
}
else
{
// fallback language
uiCulture = LocalizationManager.GetDefaultCulture();
}
}
// get site culture (content localization)
var culture = site?.CultureCode;
if (string.IsNullOrEmpty(culture))
{
culture = uiCulture;
}
_language = culture; // html element language attribute
// get culture cookie
string cultureCookie = Context.Request.Cookies[Shared.CookieRequestCultureProvider.DefaultCookieName];
if (cultureCookie != null)
{
// verify culture cookie (which could be inaccurate when using subfolders ie. domain.com/fr as cookies are based on domain)
var requestCulture = Shared.CookieRequestCultureProvider.ParseCookieValue(cultureCookie);
if (culture != requestCulture.Culture.Name || uiCulture != requestCulture.UICulture.Name)
{
// convert to culture cookie format (ie. "c=en|uic=en") and save cookie
cultureCookie = Shared.CookieRequestCultureProvider.MakeCookieValue(new Models.RequestCulture(culture, uiCulture));
SetLocalizationCookie(cultureCookie);
}
}
else
{
// convert to culture cookie format (ie. "c=en|uic=en") and save cookie
cultureCookie = Shared.CookieRequestCultureProvider.MakeCookieValue(new Models.RequestCulture(culture, uiCulture));
SetLocalizationCookie(cultureCookie);
}
}
}

View File

@@ -43,7 +43,7 @@ namespace Oqtane.Controllers
int SiteId;
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId)
{
var hierarchy = GetFoldersHierarchy(_folders.GetFolders(SiteId).ToList());
var hierarchy = _folders.GetFolders(SiteId).ToList();
foreach (Folder folder in hierarchy)
{
// note that Browse permission is used for this method
@@ -281,47 +281,5 @@ namespace Oqtane.Controllers
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
private static List<Folder> GetFoldersHierarchy(List<Folder> folders)
{
List<Folder> hierarchy = new List<Folder>();
Action<List<Folder>, Folder> getPath = null;
getPath = (folderList, folder) =>
{
IEnumerable<Folder> children;
int level;
if (folder == null)
{
level = -1;
children = folders.Where(item => item.ParentId == null);
}
else
{
level = folder.Level;
children = folders.Where(item => item.ParentId == folder.FolderId);
}
foreach (Folder child in children)
{
child.Level = level + 1;
child.HasChildren = folders.Any(item => item.ParentId == child.FolderId);
hierarchy.Add(child);
getPath(folderList, child);
}
};
folders = folders.OrderBy(item => item.Name).ToList();
getPath(folders, null);
// add any non-hierarchical items to the end of the list
foreach (Folder folder in folders)
{
if (hierarchy.Find(item => item.FolderId == folder.FolderId) == null)
{
hierarchy.Add(folder);
}
}
return hierarchy;
}
}
}

View File

@@ -19,26 +19,54 @@ namespace Oqtane.Controllers
_localizationManager = localizationManager;
}
// GET: api/localization
// GET: api/localization?installed=true/false
[HttpGet()]
public IEnumerable<Culture> Get(bool installed)
{
string[] culturecodes;
string[] cultureCodes;
if (installed)
{
culturecodes = _localizationManager.GetInstalledCultures();
cultureCodes = _localizationManager.GetInstalledCultures();
}
else
{
culturecodes = _localizationManager.GetSupportedCultures();
cultureCodes = _localizationManager.GetSupportedCultures();
}
var cultures = culturecodes.Select(c => new Culture
var cultures = cultureCodes.Select(c => new Culture
{
Name = CultureInfo.GetCultureInfo(c).Name,
DisplayName = CultureInfo.GetCultureInfo(c).DisplayName,
IsDefault = _localizationManager.GetDefaultCulture()
.Equals(CultureInfo.GetCultureInfo(c).Name, StringComparison.OrdinalIgnoreCase)
});
}).ToList();
if (cultures.Count == 0)
{
cultures.Add(new Culture { Name = "en", DisplayName = "English", IsDefault = true });
}
return cultures.OrderBy(item => item.DisplayName);
}
// GET: api/localization/neutral
[HttpGet("neutral")]
public IEnumerable<Culture> Get()
{
var cultureCodes = _localizationManager.GetNeutralCultures();
var cultures = cultureCodes.Select(c => new Culture
{
Name = CultureInfo.GetCultureInfo(c).Name,
DisplayName = CultureInfo.GetCultureInfo(c).DisplayName,
IsDefault = false
}).ToList();
if (cultures.Count == 0)
{
cultures.Add(new Culture { Name = "en", DisplayName = "English", IsDefault = false });
}
return cultures.OrderBy(item => item.DisplayName);
}
}

View File

@@ -164,6 +164,7 @@ namespace Oqtane.Controllers
{
_moduleDefinitions.UpdateModuleDefinition(moduleDefinition);
_syncManager.AddSyncEvent(_alias, EntityNames.ModuleDefinition, moduleDefinition.ModuleDefinitionId, SyncEventActions.Update);
_syncManager.AddSyncEvent(_alias, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh); // fingerprint changed
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Module Definition Updated {ModuleDefinition}", moduleDefinition);
}
else

View File

@@ -1,16 +1,15 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Oqtane.Models;
using Oqtane.Shared;
using System.Linq;
using Oqtane.Security;
using System.Net;
using System.Security;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Oqtane.Enums;
using Oqtane.Infrastructure;
using Oqtane.Models;
using Oqtane.Repository;
using System.Xml.Linq;
using Microsoft.AspNetCore.Diagnostics;
using Oqtane.Security;
using Oqtane.Shared;
namespace Oqtane.Controllers
{
@@ -498,6 +497,91 @@ namespace Oqtane.Controllers
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
}
// POST api/<controller>/5/6
[HttpPost("{fromPageId}/{toPageId}/{usePagePermissions}")]
[Authorize(Roles = RoleNames.Registered)]
public void Post(int fromPageId, int toPageId, bool usePagePermissions)
{
var fromPage = _pages.GetPage(fromPageId);
if (fromPage != null && fromPage.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.View, fromPage.PermissionList))
{
var toPage = _pages.GetPage(toPageId);
if (toPage != null && toPage.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.View, toPage.PermissionList))
{
// copy modules
List<PageModule> pageModules = _pageModules.GetPageModules(fromPage.SiteId).ToList();
foreach (PageModule pm in pageModules.Where(item => item.PageId == fromPage.PageId && !item.Module.AllPages && !item.IsDeleted))
{
Module module;
// determine if module is a shared instance (ie. exists on other pages)
if (!pageModules.Any(item => item.ModuleId == pm.ModuleId && item.PageId != fromPage.PageId))
{
// create new module
module = new Module();
module.SiteId = fromPage.SiteId;
module.PageId = toPageId;
module.ModuleDefinitionName = pm.Module.ModuleDefinitionName;
module.AllPages = false;
if (usePagePermissions)
{
module.PermissionList = toPage.PermissionList;
}
else
{
module.PermissionList = pm.Module.PermissionList;
}
module.PermissionList = module.PermissionList.Select(item => new Permission
{
SiteId = item.SiteId,
EntityName = EntityNames.Module,
EntityId = -1,
PermissionName = item.PermissionName,
RoleName = item.RoleName,
UserId = item.UserId,
IsAuthorized = item.IsAuthorized,
}).ToList();
module = _modules.AddModule(module);
string content = _modules.ExportModule(pm.ModuleId);
if (content != "")
{
_modules.ImportModule(module.ModuleId, content);
}
}
else
{
// use existing module
module = pm.Module;
}
PageModule pageModule = new PageModule();
pageModule.PageId = toPageId;
pageModule.ModuleId = module.ModuleId;
pageModule.Title = pm.Title;
pageModule.Pane = pm.Pane;
pageModule.Order = pm.Order;
pageModule.ContainerType = pm.ContainerType;
pageModule.EffectiveDate = pm.EffectiveDate;
pageModule.ExpiryDate = pm.ExpiryDate;
pageModule.Header = pm.Header;
pageModule.Footer = pm.Footer;
_pageModules.AddPageModule(pageModule);
}
_syncManager.AddSyncEvent(_alias, EntityNames.Site, fromPage.SiteId, SyncEventActions.Refresh);
}
else
{
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
else
{
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
}
}

View File

@@ -0,0 +1,135 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Security.Policy;
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.ApiRoute)]
public class SiteGroupController : Controller
{
private readonly ISiteGroupRepository _siteGroupRepository;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger;
private readonly Alias _alias;
public SiteGroupController(ISiteGroupRepository siteGroupRepository, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager)
{
_siteGroupRepository = siteGroupRepository;
_syncManager = syncManager;
_logger = logger;
_alias = tenantManager.GetAlias();
}
// GET: api/<controller>?siteid=x
[HttpGet]
[Authorize(Roles = RoleNames.Admin)]
public IEnumerable<SiteGroup> Get(string siteid)
{
if (User.IsInRole(RoleNames.Host) || (int.TryParse(siteid, out int SiteId) && SiteId == _alias.SiteId))
{
var siteGroups = _siteGroupRepository.GetSiteGroups();
if (!User.IsInRole(RoleNames.Host))
{
siteGroups = siteGroups.Where(item => item.PrimarySiteId == _alias.SiteId);
}
return siteGroups.ToList();
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Site Group Get Attempt {SiteId}", siteid);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
}
// GET api/<controller>/5
[HttpGet("{id}")]
[Authorize(Roles = RoleNames.Host)]
public SiteGroup Get(int id)
{
var group = _siteGroupRepository.GetSiteGroup(id);
if (group != null)
{
return group;
}
else
{
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
return null;
}
}
// POST api/<controller>
[HttpPost]
[Authorize(Roles = RoleNames.Host)]
public SiteGroup Post([FromBody] SiteGroup siteGroup)
{
if (ModelState.IsValid)
{
siteGroup = _siteGroupRepository.AddSiteGroup(siteGroup);
_syncManager.AddSyncEvent(_alias, EntityNames.SiteGroup, siteGroup.SiteGroupId, SyncEventActions.Create);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Site Group Added {Group}", siteGroup);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Site Group Post Attempt {Group}", siteGroup);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
siteGroup = null;
}
return siteGroup;
}
// PUT api/<controller>/5
[HttpPut("{id}")]
[Authorize(Roles = RoleNames.Admin)]
public SiteGroup Put(int id, [FromBody] SiteGroup siteGroup)
{
if (ModelState.IsValid && siteGroup.SiteGroupId == id)
{
if (!User.IsInRole(RoleNames.Host) && siteGroup.Synchronize)
{
// admins can only update the synchronize field
siteGroup = _siteGroupRepository.GetSiteGroup(siteGroup.SiteGroupId, false);
siteGroup.Synchronize = true;
}
siteGroup = _siteGroupRepository.UpdateSiteGroup(siteGroup);
_syncManager.AddSyncEvent(_alias, EntityNames.SiteGroup, siteGroup.SiteGroupId, SyncEventActions.Update);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Site Group Updated {Group}", siteGroup);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Site Group Put Attempt {Group}", siteGroup);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
siteGroup = null;
}
return siteGroup;
}
// DELETE api/<controller>/5
[HttpDelete("{id}")]
[Authorize(Roles = RoleNames.Host)]
public void Delete(int id)
{
var siteGroup = _siteGroupRepository.GetSiteGroup(id);
if (siteGroup != null)
{
_siteGroupRepository.DeleteSiteGroup(id);
_syncManager.AddSyncEvent(_alias, EntityNames.SiteGroup, siteGroup.SiteGroupId, SyncEventActions.Delete);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Site Group Deleted {siteGroupId}", id);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Site Group Delete Attempt {siteGroupId}", id);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
}
}

View File

@@ -0,0 +1,126 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
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.ApiRoute)]
public class SiteGroupMemberController : Controller
{
private readonly ISiteGroupMemberRepository _siteGroupMemberRepository;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger;
private readonly Alias _alias;
public SiteGroupMemberController(ISiteGroupMemberRepository siteGroupMemberRepository, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager)
{
_siteGroupMemberRepository = siteGroupMemberRepository;
_syncManager = syncManager;
_logger = logger;
_alias = tenantManager.GetAlias();
}
// GET: api/<controller>?siteid=x&groupid=y
[HttpGet]
[Authorize(Roles = RoleNames.Host)]
public IEnumerable<SiteGroupMember> Get(string siteid, string groupid)
{
if (int.TryParse(siteid, out int SiteId) && int.TryParse(groupid, out int SiteGroupId))
{
return _siteGroupMemberRepository.GetSiteGroupMembers(SiteId, SiteGroupId).ToList();
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Site Group Member Get Attempt for SiteId {SiteId} And SiteGroupId {SiteGroupId}", siteid, groupid);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
}
// GET api/<controller>/5
[HttpGet("{id}")]
[Authorize(Roles = RoleNames.Host)]
public SiteGroupMember Get(int id)
{
var siteGroupMember = _siteGroupMemberRepository.GetSiteGroupMember(id);
if (siteGroupMember != null)
{
return siteGroupMember;
}
else
{
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
return null;
}
}
// POST api/<controller>
[HttpPost]
[Authorize(Roles = RoleNames.Host)]
public SiteGroupMember Post([FromBody] SiteGroupMember siteGroupMember)
{
if (ModelState.IsValid)
{
siteGroupMember = _siteGroupMemberRepository.AddSiteGroupMember(siteGroupMember);
_syncManager.AddSyncEvent(_alias, EntityNames.SiteGroupMember, siteGroupMember.SiteGroupId, SyncEventActions.Create);
_syncManager.AddSyncEvent(_alias, EntityNames.Site, siteGroupMember.SiteId, SyncEventActions.Refresh);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Site Group Member Added {SiteGroupMember}", siteGroupMember);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Site Group Member Post Attempt {SiteGroupMember}", siteGroupMember);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
siteGroupMember = null;
}
return siteGroupMember;
}
// PUT api/<controller>/5
[HttpPut("{id}")]
[Authorize(Roles = RoleNames.Host)]
public SiteGroupMember Put(int id, [FromBody] SiteGroupMember siteGroupMember)
{
if (ModelState.IsValid && siteGroupMember.SiteGroupId == id && _siteGroupMemberRepository.GetSiteGroupMember(siteGroupMember.SiteGroupId, false) != null)
{
siteGroupMember = _siteGroupMemberRepository.UpdateSiteGroupMember(siteGroupMember);
_syncManager.AddSyncEvent(_alias, EntityNames.SiteGroupMember, siteGroupMember.SiteGroupId, SyncEventActions.Update);
_syncManager.AddSyncEvent(_alias, EntityNames.Site, siteGroupMember.SiteId, SyncEventActions.Refresh);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Site Group Member Updated {SiteGroupMember}", siteGroupMember);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Site Group Member Put Attempt {SiteGroupMember}", siteGroupMember);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
siteGroupMember = null;
}
return siteGroupMember;
}
// DELETE api/<controller>/5
[HttpDelete("{id}")]
[Authorize(Roles = RoleNames.Host)]
public void Delete(int id)
{
var siteGroupMember = _siteGroupMemberRepository.GetSiteGroupMember(id);
if (siteGroupMember != null)
{
_siteGroupMemberRepository.DeleteSiteGroupMember(id);
_syncManager.AddSyncEvent(_alias, EntityNames.SiteGroupMember, siteGroupMember.SiteGroupMemberId, SyncEventActions.Delete);
_syncManager.AddSyncEvent(_alias, EntityNames.Site, siteGroupMember.SiteId, SyncEventActions.Refresh);
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Site Group Member Deleted {SiteGroupMemberId}", id);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Site Group Member Delete Attempt {SiteGroupMemberId}", id);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}
}
}
}

View File

@@ -0,0 +1,63 @@
using System.Net;
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.ApiRoute)]
public class SiteTaskController : Controller
{
private readonly ISiteTaskRepository _siteTasks;
private readonly ILogManager _logger;
private readonly Alias _alias;
public SiteTaskController(ISiteTaskRepository siteTasks, ILogManager logger, ITenantManager tenantManager)
{
_siteTasks = siteTasks;
_logger = logger;
_alias = tenantManager.GetAlias();
}
// GET api/<controller>/5
[HttpGet("{id}")]
[Authorize(Roles = RoleNames.Admin)]
public SiteTask Get(int id)
{
var siteTask = _siteTasks.GetSiteTask(id);
if (siteTask.SiteId == _alias.SiteId)
{
return siteTask;
}
else
{
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
}
// POST api/<controller>
[HttpPost]
[Authorize(Roles = RoleNames.Admin)]
public SiteTask Post([FromBody] SiteTask siteTask)
{
if (ModelState.IsValid && siteTask.SiteId == _alias.SiteId)
{
siteTask.IsCompleted = false;
siteTask = _siteTasks.AddSiteTask(siteTask);
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Site Task Added {SiteTask}", siteTask);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Site Task Post Attempt {SiteTask}", siteTask);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
siteTask = null;
}
return siteTask;
}
}
}

View File

@@ -117,6 +117,7 @@ namespace Oqtane.Controllers
{
_themes.UpdateTheme(theme);
_syncManager.AddSyncEvent(_alias, EntityNames.Theme, theme.ThemeId, SyncEventActions.Update);
_syncManager.AddSyncEvent(_alias, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh); // fingerprint changed
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Theme Updated {Theme}", theme);
}
else

View File

@@ -418,42 +418,6 @@ namespace Oqtane.Controllers
return requirements;
}
// POST api/<controller>/import?siteid=x&fileid=y&notify=z
[HttpPost("import")]
[Authorize(Roles = RoleNames.Admin)]
public async Task<Dictionary<string, string>> Import(string siteid, string fileid, string notify)
{
if (int.TryParse(siteid, out int SiteId) && SiteId == _tenantManager.GetAlias().SiteId && int.TryParse(fileid, out int FileId) && bool.TryParse(notify, out bool Notify))
{
var file = _files.GetFile(FileId);
if (file != null)
{
if (_userPermissions.IsAuthorized(User, PermissionNames.View, file.Folder.PermissionList))
{
return await _userManager.ImportUsers(SiteId, _files.GetFilePath(file), Notify);
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Import Attempt {SiteId} {FileId}", siteid, fileid);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Import File Does Not Exist {SiteId} {FileId}", siteid, fileid);
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
return null;
}
}
else
{
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Import Attempt {SiteId} {FileId}", siteid, fileid);
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return null;
}
}
// GET: api/<controller>/passkey?id=x
[HttpGet("passkey")]
[Authorize]

View File

@@ -233,6 +233,9 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddScoped<ICookieConsentService, ServerCookieConsentService>();
services.AddScoped<ITimeZoneService, TimeZoneService>();
services.AddScoped<IMigrationHistoryService, MigrationHistoryService>();
services.AddScoped<ISiteGroupService, SiteGroupService>();
services.AddScoped<ISiteGroupMemberService, SiteGroupMemberService>();
services.AddScoped<ISiteTaskService, SiteTaskService>();
// providers
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.QuillJSTextEditor>();
@@ -282,6 +285,9 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddTransient<IUrlMappingRepository, UrlMappingRepository>();
services.AddTransient<ISearchContentRepository, SearchContentRepository>();
services.AddTransient<IMigrationHistoryRepository, MigrationHistoryRepository>();
services.AddTransient<ISiteGroupRepository, SiteGroupRepository>();
services.AddTransient<ISiteGroupMemberRepository, SiteGroupMemberRepository>();
services.AddTransient<ISiteTaskRepository, SiteTaskRepository>();
// managers
services.AddTransient<IDBContextDependencies, DBContextDependencies>();

View File

@@ -594,7 +594,8 @@ namespace Oqtane.Infrastructure
Prerender = (rendermode == RenderModes.Interactive),
Hybrid = false,
EnhancedNavigation = true,
TenantId = tenant.TenantId
CultureCode = "en",
TenantId = tenant.TenantId // required for site creation
};
site = sites.AddSite(site);

View File

@@ -22,7 +22,6 @@ namespace Oqtane.Infrastructure
{
void InstallPackages();
bool UninstallPackage(string PackageName);
int RegisterAssemblies();
Task UpgradeFramework(bool backup);
void RestartApplication();
}
@@ -61,10 +60,6 @@ namespace Oqtane.Infrastructure
Directory.CreateDirectory(sourceFolder);
}
// read assembly log
var assemblyLogPath = Path.Combine(sourceFolder, "assemblies.log");
var assemblies = GetAssemblyLog(assemblyLogPath);
// install Nuget packages in secure Packages folder
var packages = Directory.GetFiles(sourceFolder, "*.nupkg");
foreach (string packagename in packages)
@@ -162,27 +157,6 @@ namespace Oqtane.Infrastructure
{
manifest = true;
}
// register assembly
if (Path.GetExtension(filename) == ".dll")
{
// do not register licensing assemblies
if (!Path.GetFileName(filename).StartsWith("Oqtane.Licensing."))
{
// if package version was not installed previously
if (!File.Exists(Path.Combine(sourceFolder, name + ".log")))
{
if (assemblies.ContainsKey(Path.GetFileName(filename)))
{
assemblies[Path.GetFileName(filename)] += 1;
}
else
{
assemblies.Add(Path.GetFileName(filename), 1);
}
}
}
}
}
}
@@ -212,12 +186,6 @@ namespace Oqtane.Infrastructure
File.Delete(packagename);
}
if (packages.Length != 0)
{
// save assembly log
SetAssemblyLog(assemblyLogPath, assemblies);
}
return errors;
}
@@ -270,10 +238,6 @@ namespace Oqtane.Infrastructure
{
if (!string.IsNullOrEmpty(PackageName))
{
// read assembly log
var assemblyLogPath = Path.Combine(Path.Combine(_environment.ContentRootPath, Constants.PackagesFolder), "assemblies.log");
var assemblies = GetAssemblyLog(assemblyLogPath);
// get manifest with highest version
string packagename = "";
string[] packages = Directory.GetFiles(Path.Combine(_environment.ContentRootPath, Constants.PackagesFolder), PackageName + "*.log");
@@ -298,23 +262,7 @@ namespace Oqtane.Infrastructure
// do not remove licensing assemblies
if (!Path.GetFileName(filepath).StartsWith("Oqtane.Licensing."))
{
// use assembly log to determine if assembly is used in other packages
if (assemblies.ContainsKey(Path.GetFileName(filepath)))
{
if (assemblies[Path.GetFileName(filepath)] == 1)
{
DeleteFile(filepath);
assemblies.Remove(Path.GetFileName(filepath));
}
else
{
assemblies[Path.GetFileName(filepath)] -= 1;
}
}
else // does not exist in assembly log
{
DeleteFile(filepath);
}
DeleteFile(filepath);
}
}
else // not an assembly
@@ -329,9 +277,6 @@ namespace Oqtane.Infrastructure
File.Delete(asset);
}
// save assembly log
SetAssemblyLog(assemblyLogPath, assemblies);
return true;
}
}
@@ -351,64 +296,6 @@ namespace Oqtane.Infrastructure
}
}
public int RegisterAssemblies()
{
var assemblyLogPath = GetAssemblyLogPath();
var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
var assemblies = GetAssemblyLog(assemblyLogPath);
// remove assemblies that no longer exist
foreach (var dll in assemblies)
{
if (!File.Exists(Path.Combine(binFolder, dll.Key)))
{
assemblies.Remove(dll.Key);
}
}
// add assemblies which are not registered
foreach (var dll in Directory.GetFiles(binFolder, "*.dll"))
{
if (!assemblies.ContainsKey(Path.GetFileName(dll)))
{
assemblies.Add(Path.GetFileName(dll), 1);
}
}
SetAssemblyLog(assemblyLogPath, assemblies);
return assemblies.Count;
}
private string GetAssemblyLogPath()
{
string packagesFolder = Path.Combine(_environment.ContentRootPath, Constants.PackagesFolder);
if (!Directory.Exists(packagesFolder))
{
Directory.CreateDirectory(packagesFolder);
}
return Path.Combine(packagesFolder, "assemblies.log");
}
private static Dictionary<string, int> GetAssemblyLog(string assemblyLogPath)
{
Dictionary<string, int> assemblies = new Dictionary<string, int>();
if (File.Exists(assemblyLogPath))
{
assemblies = JsonSerializer.Deserialize<Dictionary<string, int>>(File.ReadAllText(assemblyLogPath));
}
return assemblies;
}
private static void SetAssemblyLog(string assemblyLogPath, Dictionary<string, int> assemblies)
{
if (File.Exists(assemblyLogPath))
{
File.Delete(assemblyLogPath);
}
File.WriteAllText(assemblyLogPath, JsonSerializer.Serialize(assemblies, new JsonSerializerOptions { WriteIndented = true }));
}
public async Task UpgradeFramework(bool backup)
{
string folder = Path.Combine(_environment.ContentRootPath, Constants.PackagesFolder);

View File

@@ -0,0 +1,13 @@
using System;
using System.Threading.Tasks;
using Oqtane.Models;
namespace Oqtane.Infrastructure
{
public interface ISiteTask
{
string ExecuteTask(IServiceProvider provider, Site site, string parameters);
Task<string> ExecuteTaskAsync(IServiceProvider provider, Site site, string parameters);
}
}

View File

@@ -38,7 +38,7 @@ namespace Oqtane.Infrastructure
// iterate through sites for current tenant
List<Site> sites = siteRepository.GetSites().ToList();
foreach (Site site in sites)
foreach (Site site in sites.Where(item => !item.IsDeleted))
{
log += "Processing Notifications For Site: " + site.Name + "<br />";
@@ -48,7 +48,7 @@ namespace Oqtane.Infrastructure
// get site settings
var settings = settingRepository.GetSettings(EntityNames.Site, site.SiteId, EntityNames.Host, -1);
if (!site.IsDeleted && settingRepository.GetSettingValue(settings, "SMTPEnabled", "True") == "True")
if (settingRepository.GetSettingValue(settings, "SMTPEnabled", "True") == "True")
{
bool valid = true;
if (settingRepository.GetSettingValue(settings, "SMTPAuthentication", "Basic") == "Basic")
@@ -121,11 +121,23 @@ namespace Oqtane.Infrastructure
{
if (settingRepository.GetSettingValue(settings, "SMTPAuthentication", "Basic") == "Basic")
{
// it is possible to use basic without any authentication (not recommended)
if (settingRepository.GetSettingValue(settings, "SMTPUsername", "") != "" && settingRepository.GetSettingValue(settings, "SMTPPassword", "") != "")
{
await client.AuthenticateAsync(settingRepository.GetSettingValue(settings, "SMTPUsername", ""),
try
{
await client.AuthenticateAsync(settingRepository.GetSettingValue(settings, "SMTPUsername", ""),
settingRepository.GetSettingValue(settings, "SMTPPassword", ""));
}
catch (Exception ex)
{
log += "SMTP Not Configured Properly In Site Settings - Basic Authentication Failed Using Username And Password - " + ex.Message + "<br />";
valid = false;
}
}
else
{
// it is possible to use basic without any authentication (not recommended)
}
}
else
@@ -278,7 +290,7 @@ namespace Oqtane.Infrastructure
}
else
{
log += "Site Deleted Or SMTP Disabled In Site Settings<br />";
log += "SMTP Disabled In Site Settings<br />";
}
}
else

View File

@@ -33,11 +33,11 @@ namespace Oqtane.Infrastructure
var visitorRepository = provider.GetRequiredService<IVisitorRepository>();
var notificationRepository = provider.GetRequiredService<INotificationRepository>();
var urlMappingRepository = provider.GetRequiredService<IUrlMappingRepository>();
var installationManager = provider.GetRequiredService<IInstallationManager>();
var siteTaskRepository = provider.GetRequiredService<ISiteTaskRepository>();
// iterate through sites for current tenant
List<Site> sites = siteRepository.GetSites().ToList();
foreach (Site site in sites)
foreach (Site site in sites.Where(item => !item.IsDeleted))
{
log += "<br />Processing Site: " + site.Name + "<br />";
int count;
@@ -95,17 +95,18 @@ namespace Oqtane.Infrastructure
{
log += $"Error Purging Broken Urls - {ex.Message}<br />";
}
}
// register assemblies
try
{
var assemblies = installationManager.RegisterAssemblies();
log += "<br />" + assemblies.ToString() + " Assemblies Registered<br />";
}
catch (Exception ex)
{
log += $"<br />Error Registering Assemblies - {ex.Message}<br />";
// purge completed site tasks
retention = 30; // 30 day default
try
{
count = siteTaskRepository.DeleteSiteTasks(site.SiteId, retention);
log += count.ToString() + " Completed Tasks Purged<br />";
}
catch (Exception ex)
{
log += $"Error Purging Completed Site Tasks - {ex.Message}<br />";
}
}
return log;

View File

@@ -40,7 +40,7 @@ namespace Oqtane.Infrastructure
var searchService = provider.GetRequiredService<ISearchService>();
var sites = siteRepository.GetSites().ToList();
foreach (var site in sites)
foreach (var site in sites.Where(item => !item.IsDeleted))
{
log += $"Indexing Site: {site.Name}<br />";

View File

@@ -0,0 +1,84 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Oqtane.Repository;
namespace Oqtane.Infrastructure
{
public class SiteTaskJob : HostedServiceBase
{
public SiteTaskJob(IServiceScopeFactory serviceScopeFactory) : base(serviceScopeFactory)
{
Name = "Site Task Job";
Frequency = "m"; // run every minute
Interval = 1;
IsEnabled = true;
}
// job is executed for each tenant in installation
public override async Task<string> ExecuteJobAsync(IServiceProvider provider)
{
var log = "";
// resolve services
var tenantManager = provider.GetRequiredService<ITenantManager>();
var siteRepository = provider.GetRequiredService<ISiteRepository>();
var siteTaskRepository = provider.GetRequiredService<ISiteTaskRepository>();
var tenant = tenantManager.GetTenant();
// iterate through sites for current tenant
var sites = siteRepository.GetSites().ToList();
foreach (var site in sites.Where(item => !item.IsDeleted))
{
log += $"Processing Site: {site.Name}<br />";
// get incomplete tasks for site
var tasks = siteTaskRepository.GetSiteTasks(site.SiteId).ToList();
if (tasks != null && tasks.Any())
{
foreach (var task in tasks)
{
log += $"Executing Task: {task.Name}<br />";
Type taskType = Type.GetType(task.Type);
if (taskType != null && taskType.GetInterface(nameof(ISiteTask)) != null)
{
try
{
tenantManager.SetAlias(tenant.TenantId, site.SiteId);
var taskObject = ActivatorUtilities.CreateInstance(provider, taskType);
var taskLog = ((ISiteTask)taskObject).ExecuteTask(provider, site, task.Parameters);
taskLog += await ((ISiteTask)taskObject).ExecuteTaskAsync(provider, site, task.Parameters);
task.Status = taskLog;
}
catch (Exception ex)
{
task.Status = "Error: " + ex.Message;
}
}
else
{
task.Status = $"Error: Task {task.Name} Has An Invalid Type {task.Type}<br />";
}
// update task
task.IsCompleted = true;
siteTaskRepository.UpdateSiteTask(task);
log += task.Status + "<br />";
}
}
else
{
log += "No Tasks To Execute<br />";
}
}
return log;
}
}
}

View File

@@ -0,0 +1,895 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Oqtane.Models;
using Oqtane.Modules;
using Oqtane.Repository;
using Oqtane.Shared;
namespace Oqtane.Infrastructure
{
public class SynchronizationJob : HostedServiceBase
{
// JobType = "Oqtane.Infrastructure.SynchronizationJob, Oqtane.Server"
// synchronization only supports sites in the same tenant (database)
// module title is used as a key to identify module instances on a page
// modules must implement ISynchronizable interface for content synchronization
// change detection does not support deleted items as key values will usually be different due to localization
// define settings that should not be synchronized (should be extensible in the future)
List<Setting> excludedSettings = new List<Setting>() {
new Setting { EntityName = EntityNames.Site, SettingName = "Search_LastIndexedOn" }
};
public SynchronizationJob(IServiceScopeFactory serviceScopeFactory) : base(serviceScopeFactory)
{
Name = "Synchronization Job";
Frequency = "m"; // minute
Interval = 1;
IsEnabled = false;
}
// job is executed for each tenant in installation
public override string ExecuteJob(IServiceProvider provider)
{
string log = "";
var siteGroupRepository = provider.GetRequiredService<ISiteGroupRepository>();
var siteGroupMemberRepository = provider.GetRequiredService<ISiteGroupMemberRepository>();
var siteRepository = provider.GetRequiredService<ISiteRepository>();
var aliasRepository = provider.GetRequiredService<IAliasRepository>();
var tenantManager = provider.GetRequiredService<ITenantManager>();
var settingRepository = provider.GetRequiredService<ISettingRepository>();
List<SiteGroupMember> siteGroupMembers = null;
List<Site> sites = null;
List<Alias> aliases = null;
// get site groups
var siteGroups = siteGroupRepository.GetSiteGroups();
// iterate through site groups which need to be synchronized
foreach (var siteGroup in siteGroups.Where(item => item.Synchronize && (item.Type == SiteGroupTypes.Synchronization || item.Type == SiteGroupTypes.ChangeDetection)))
{
// get data
if (siteGroupMembers == null)
{
siteGroupMembers = siteGroupMemberRepository.GetSiteGroupMembers().ToList();
sites = siteRepository.GetSites().ToList();
aliases = aliasRepository.GetAliases().ToList();
}
var primaryAliasName = "https://" + aliases.First(item => item.TenantId == tenantManager.GetTenant().TenantId && item.SiteId == siteGroup.PrimarySiteId && item.IsDefault).Name;
log += (siteGroup.Type == SiteGroupTypes.Synchronization) ? "Synchronizing " : "Comparing ";
log += $"Primary Site: {sites.First(item => item.SiteId == siteGroup.PrimarySiteId).Name} - {CreateLink(primaryAliasName)}<br />";
// get primary site
var primarySite = sites.FirstOrDefault(item => item.SiteId == siteGroup.PrimarySiteId);
if (primarySite != null && !primarySite.IsDeleted)
{
// update flag to prevent job from processing group again
siteGroup.Synchronize = false;
siteGroupRepository.UpdateSiteGroup(siteGroup);
// iterate through sites in site group
foreach (var siteGroupMember in siteGroupMembers.Where(item => item.SiteGroupId == siteGroup.SiteGroupId && item.SiteId != siteGroup.PrimarySiteId))
{
// get secondary site
var secondarySite = sites.FirstOrDefault(item => item.SiteId == siteGroupMember.SiteId);
if (secondarySite != null)
{
// get default alias for site
var secondaryAliasName = "https://" + aliases.First(item => item.TenantId == tenantManager.GetTenant().TenantId && item.SiteId == siteGroupMember.SiteId && item.IsDefault).Name;
siteGroupMember.AliasName = (siteGroupMember.SiteGroup.Type == SiteGroupTypes.Synchronization) ? secondaryAliasName : primaryAliasName;
// initialize SynchronizedOn
if (siteGroupMember.SynchronizedOn == null)
{
siteGroupMember.SynchronizedOn = DateTime.MinValue;
}
// synchronize site
var siteLog = SynchronizeSite(provider, tenantManager, settingRepository, siteGroupMember, primarySite, secondarySite);
if (string.IsNullOrEmpty(siteLog))
{
siteLog = (siteGroupMember.SynchronizedOn != DateTime.MinValue) ? "No Changes Identified<br />" : "Initialization Complete<br />";
}
// set synchronized date/time
siteGroupMember.SynchronizedOn = DateTime.UtcNow;
siteGroupMemberRepository.UpdateSiteGroupMember(siteGroupMember);
log += $"With Secondary Site: {secondarySite.Name} - {CreateLink(secondaryAliasName)}<br />" + siteLog;
}
else
{
log += $"Site Group {siteGroup.Name} Has A SiteId {siteGroupMember.SiteId} Which Does Not Exist<br />";
}
}
}
else
{
log += $"Site Group {siteGroup.Name} Has A PrimarySiteId {siteGroup.PrimarySiteId} Which Does Not Exist Or Is Deleted<br />";
}
}
if (string.IsNullOrEmpty(log))
{
log = "No Site Groups Require Synchronization<br />";
}
return log;
}
private string SynchronizeSite(IServiceProvider provider, ITenantManager tenantManager, ISettingRepository settingRepository, SiteGroupMember siteGroupMember, Site primarySite, Site secondarySite)
{
var log = "";
// synchronize roles
log += SynchronizeRoles(provider, settingRepository, siteGroupMember, primarySite.SiteId, secondarySite.SiteId);
// synchronize folders/files
log += SynchronizeFolders(provider, settingRepository, siteGroupMember, primarySite.SiteId, secondarySite.SiteId);
// synchronize pages/modules
log += SynchronizePages(provider, settingRepository, tenantManager, siteGroupMember, primarySite.SiteId, secondarySite.SiteId);
// synchronize site
if (primarySite.ModifiedOn > siteGroupMember.SynchronizedOn)
{
if (siteGroupMember.SiteGroup.Type == SiteGroupTypes.Synchronization)
{
secondarySite.TimeZoneId = primarySite.TimeZoneId;
secondarySite.CultureCode = primarySite.CultureCode;
if (secondarySite.LogoFileId != primarySite.LogoFileId)
{
secondarySite.LogoFileId = ResolveFileId(provider, primarySite.LogoFileId, secondarySite.SiteId);
}
if (secondarySite.FaviconFileId != primarySite.FaviconFileId)
{
secondarySite.FaviconFileId = ResolveFileId(provider, primarySite.FaviconFileId, secondarySite.SiteId); ;
}
secondarySite.DefaultThemeType = primarySite.DefaultThemeType;
secondarySite.DefaultContainerType = primarySite.DefaultContainerType;
secondarySite.AdminContainerType = primarySite.AdminContainerType;
secondarySite.PwaIsEnabled = primarySite.PwaIsEnabled;
if (secondarySite.PwaAppIconFileId != primarySite.PwaAppIconFileId)
{
secondarySite.PwaAppIconFileId = ResolveFileId(provider, primarySite.PwaAppIconFileId, secondarySite.SiteId); ;
}
if (secondarySite.PwaSplashIconFileId != primarySite.PwaSplashIconFileId)
{
secondarySite.PwaSplashIconFileId = ResolveFileId(provider, primarySite.PwaSplashIconFileId, secondarySite.SiteId); ;
}
secondarySite.AllowRegistration = primarySite.AllowRegistration;
secondarySite.VisitorTracking = primarySite.VisitorTracking;
secondarySite.CaptureBrokenUrls = primarySite.CaptureBrokenUrls;
secondarySite.SiteGuid = primarySite.SiteGuid;
secondarySite.RenderMode = primarySite.RenderMode;
secondarySite.Runtime = primarySite.Runtime;
secondarySite.Prerender = primarySite.Prerender;
secondarySite.Hybrid = primarySite.Hybrid;
secondarySite.EnhancedNavigation = primarySite.EnhancedNavigation;
secondarySite.Version = primarySite.Version;
secondarySite.HeadContent = primarySite.HeadContent;
secondarySite.BodyContent = primarySite.BodyContent;
secondarySite.CreatedBy = primarySite.CreatedBy;
secondarySite.CreatedOn = primarySite.CreatedOn;
secondarySite.ModifiedBy = primarySite.ModifiedBy;
secondarySite.ModifiedOn = primarySite.ModifiedOn;
secondarySite.IsDeleted = primarySite.IsDeleted;
secondarySite.DeletedBy = primarySite.DeletedBy;
secondarySite.DeletedOn = primarySite.DeletedOn;
var siteRepository = provider.GetRequiredService<ISiteRepository>();
siteRepository.UpdateSite(secondarySite);
log += Log(siteGroupMember, $"Site Updated: {secondarySite.Name} - {CreateLink(siteGroupMember.AliasName)}");
}
else // change detection
{
log += Log(siteGroupMember, $"Site Updated: {primarySite.Name} - {CreateLink(siteGroupMember.AliasName)}");
}
}
// site settings
log += SynchronizeSettings(settingRepository, siteGroupMember, EntityNames.Site, primarySite.SiteId, secondarySite.SiteId);
if (siteGroupMember.SiteGroup.Type == SiteGroupTypes.Synchronization && (siteGroupMember.SynchronizedOn == DateTime.MinValue || !string.IsNullOrEmpty(log)))
{
// clear cache for secondary site if any content was Synchronized
var syncManager = provider.GetRequiredService<ISyncManager>();
var alias = new Alias { TenantId = tenantManager.GetTenant().TenantId, SiteId = secondarySite.SiteId };
syncManager.AddSyncEvent(alias, EntityNames.Site, secondarySite.SiteId, SyncEventActions.Refresh);
}
if (siteGroupMember.SiteGroup.Type == SiteGroupTypes.ChangeDetection && !string.IsNullOrEmpty(log))
{
// send change log to administrators
log += SendNotifications(provider, siteGroupMember, secondarySite.SiteId, secondarySite.Name, log);
}
return log;
}
private int? ResolveFileId(IServiceProvider provider, int? fileId, int siteId)
{
if (fileId != null)
{
var fileRepository = provider.GetRequiredService<IFileRepository>();
var file = fileRepository.GetFile(fileId.Value);
fileId = fileRepository.GetFile(siteId, file.Folder.Path, file.Name).FileId;
}
return fileId;
}
private string SynchronizeRoles(IServiceProvider provider, ISettingRepository settingRepository, SiteGroupMember siteGroupMember, int primarySiteId, int secondarySiteId)
{
// get roles
var roleRepository = provider.GetRequiredService<IRoleRepository>();
var primaryRoles = roleRepository.GetRoles(primarySiteId);
var secondaryRoles = roleRepository.GetRoles(secondarySiteId).ToList();
var log = "";
foreach (var primaryRole in primaryRoles)
{
if (siteGroupMember.SiteGroup.Type == SiteGroupTypes.Synchronization)
{
var role = secondaryRoles.FirstOrDefault(item => item.Name == primaryRole.Name);
var secondaryRole = role;
if (secondaryRole == null)
{
secondaryRole = new Role();
secondaryRole.SiteId = secondarySiteId;
}
if (role == null || primaryRole.ModifiedOn > siteGroupMember.SynchronizedOn)
{
// set all properties
secondaryRole.Name = primaryRole.Name;
secondaryRole.Description = primaryRole.Description;
secondaryRole.IsAutoAssigned = primaryRole.IsAutoAssigned;
secondaryRole.IsSystem = primaryRole.IsSystem;
if (role == null)
{
roleRepository.AddRole(secondaryRole);
log += Log(siteGroupMember, $"Role Added: {secondaryRole.Name}");
}
else
{
roleRepository.UpdateRole(secondaryRole);
log += Log(siteGroupMember, $"Role Updated: {secondaryRole.Name}");
}
}
if (role != null)
{
secondaryRoles.Remove(role);
}
}
else // change detection
{
if (primaryRole.ModifiedOn > siteGroupMember.SynchronizedOn)
{
log += Log(siteGroupMember, $"Role Updated: {primaryRole.Name}");
}
}
}
if (siteGroupMember.SiteGroup.Type == SiteGroupTypes.Synchronization)
{
// remove roles in the secondary site which do not exist in the primary site
foreach (var secondaryRole in secondaryRoles)
{
roleRepository.DeleteRole(secondaryRole.RoleId);
log += Log(siteGroupMember, $"Role Deleted: {secondaryRole.Name}");
}
}
// settings
log += SynchronizeSettings(settingRepository, siteGroupMember, EntityNames.Role, primarySiteId, secondarySiteId);
return log;
}
private string SynchronizeFolders(IServiceProvider provider, ISettingRepository settingRepository, SiteGroupMember siteGroupMember, int primarySiteId, int secondarySiteId)
{
var folderRepository = provider.GetRequiredService<IFolderRepository>();
var fileRepository = provider.GetRequiredService<IFileRepository>();
var log = "";
// get folders (ignore personalized)
var primaryFolders = folderRepository.GetFolders(primarySiteId).Where(item => !item.Path.StartsWith("Users/"));
var secondaryFolders = folderRepository.GetFolders(secondarySiteId).Where(item => !item.Path.StartsWith("Users/")).ToList();
// iterate through folders
foreach (var primaryFolder in primaryFolders)
{
if (siteGroupMember.SiteGroup.Type == SiteGroupTypes.Synchronization)
{
var folder = secondaryFolders.FirstOrDefault(item => item.Path == primaryFolder.Path);
var secondaryFolder = folder;
if (secondaryFolder == null)
{
secondaryFolder = new Folder();
secondaryFolder.SiteId = secondarySiteId;
}
if (folder == null || primaryFolder.ModifiedOn > siteGroupMember.SynchronizedOn)
{
// set all properties
secondaryFolder.ParentId = null;
if (primaryFolder.ParentId != null)
{
var parentFolder = folderRepository.GetFolder(secondarySiteId, primaryFolders.First(item => item.FolderId == primaryFolder.ParentId).Path);
if (parentFolder != null)
{
secondaryFolder.ParentId = parentFolder.FolderId;
}
}
secondaryFolder.Type = primaryFolder.Type;
secondaryFolder.Name = primaryFolder.Name;
secondaryFolder.Order = primaryFolder.Order;
secondaryFolder.ImageSizes = primaryFolder.ImageSizes;
secondaryFolder.Capacity = primaryFolder.Capacity;
secondaryFolder.ImageSizes = primaryFolder.ImageSizes;
secondaryFolder.IsSystem = primaryFolder.IsSystem;
secondaryFolder.PermissionList = SynchronizePermissions(primaryFolder.PermissionList, secondarySiteId);
if (folder == null)
{
folderRepository.AddFolder(secondaryFolder);
log += Log(siteGroupMember, $"Folder Added: {secondaryFolder.Path}");
}
else
{
folderRepository.UpdateFolder(secondaryFolder);
log += Log(siteGroupMember, $"Folder Updated: {secondaryFolder.Path}");
}
}
if (folder != null)
{
secondaryFolders.Remove(folder);
}
// folder settings
log += SynchronizeSettings(settingRepository, siteGroupMember, EntityNames.Folder, primaryFolder.FolderId, secondaryFolder.FolderId);
// files
log += SynchronizeFiles(provider, folderRepository, fileRepository, siteGroupMember, primaryFolder, secondaryFolder);
}
else // change detection
{
if (primaryFolder.ModifiedOn > siteGroupMember.SynchronizedOn)
{
log += Log(siteGroupMember, $"Folder Updated: {primaryFolder.Path}");
// folder settings
log += SynchronizeSettings(settingRepository, siteGroupMember, EntityNames.Folder, primaryFolder.FolderId, -1);
// files
log += SynchronizeFiles(provider, folderRepository, fileRepository, siteGroupMember, primaryFolder, null);
}
}
}
if (siteGroupMember.SiteGroup.Type == SiteGroupTypes.Synchronization)
{
// remove folders in the secondary site which do not exist in the primary site
foreach (var secondaryFolder in secondaryFolders)
{
folderRepository.DeleteFolder(secondaryFolder.FolderId);
log += Log(siteGroupMember, $"Folder Deleted: {secondaryFolder.Path}");
}
}
return log;
}
private string SynchronizeFiles(IServiceProvider provider, IFolderRepository folderRepository, IFileRepository fileRepository, SiteGroupMember siteGroupMember, Folder primaryFolder, Folder secondaryFolder)
{
var log = "";
// get files for folder
var primaryFiles = fileRepository.GetFiles(primaryFolder.FolderId);
var secondaryFiles = new List<Models.File>();
if (secondaryFolder != null)
{
secondaryFiles = fileRepository.GetFiles(secondaryFolder.FolderId).ToList();
}
foreach (var primaryFile in primaryFiles)
{
if (siteGroupMember.SiteGroup.Type == SiteGroupTypes.Synchronization)
{
var file = secondaryFiles.FirstOrDefault(item => item.Name == primaryFile.Name);
var secondaryFile = file;
if (secondaryFile == null)
{
secondaryFile = new Models.File();
secondaryFile.FolderId = secondaryFolder.FolderId;
secondaryFile.Name = primaryFile.Name;
}
if (file == null || primaryFile.ModifiedOn > siteGroupMember.SynchronizedOn)
{
// set all properties
secondaryFile.Extension = primaryFile.Extension;
secondaryFile.Size = primaryFile.Size;
secondaryFile.ImageHeight = primaryFile.ImageHeight;
secondaryFile.ImageWidth = primaryFile.ImageWidth;
secondaryFile.Description = primaryFile.Description;
if (file == null)
{
fileRepository.AddFile(secondaryFile);
SynchronizeFile(folderRepository, primaryFolder, primaryFile, secondaryFolder, secondaryFile);
log += Log(siteGroupMember, $"File Added: {CreateLink(siteGroupMember.AliasName + "/" + secondaryFolder.Path + secondaryFile.Name)}");
}
else
{
fileRepository.UpdateFile(secondaryFile);
SynchronizeFile(folderRepository, primaryFolder, primaryFile, secondaryFolder, secondaryFile);
log += Log(siteGroupMember, $"File Updated: {CreateLink(siteGroupMember.AliasName + "/" + secondaryFolder.Path + secondaryFile.Name)}");
}
}
if (file != null)
{
secondaryFiles.Remove(file);
}
}
else // change detection
{
if (primaryFile.ModifiedOn > siteGroupMember.SynchronizedOn)
{
log += Log(siteGroupMember, $"File Updated: {CreateLink(siteGroupMember.AliasName + "/" + primaryFolder.Path + primaryFile.Name)}");
}
}
}
if (siteGroupMember.SiteGroup.Type == SiteGroupTypes.Synchronization)
{
// remove files in the secondary site which do not exist in the primary site
foreach (var secondaryFile in secondaryFiles)
{
fileRepository.DeleteFile(secondaryFile.FileId);
var secondaryPath = Path.Combine(folderRepository.GetFolderPath(secondaryFolder), secondaryFile.Name);
System.IO.File.Delete(secondaryPath);
log += Log(siteGroupMember, $"File Deleted: {CreateLink(siteGroupMember.AliasName + "/" + secondaryFolder.Path + secondaryFile.Name)}");
}
}
return log;
}
private void SynchronizeFile(IFolderRepository folderRepository, Folder primaryFolder, Models.File primaryFile, Folder secondaryFolder, Models.File secondaryFile)
{
var primaryPath = Path.Combine(folderRepository.GetFolderPath(primaryFolder), primaryFile.Name);
if (System.IO.File.Exists(primaryPath))
{
var secondaryPath = Path.Combine(folderRepository.GetFolderPath(secondaryFolder), secondaryFile.Name);
if (!Directory.Exists(Path.GetDirectoryName(secondaryPath)))
{
Directory.CreateDirectory(Path.GetDirectoryName(secondaryPath));
}
System.IO.File.Copy(primaryPath, secondaryPath, true);
}
}
private string SynchronizePages(IServiceProvider provider, ISettingRepository settingRepository, ITenantManager tenantManager, SiteGroupMember siteGroupMember, int primarySiteId, int secondarySiteId)
{
var pageRepository = provider.GetRequiredService<IPageRepository>();
var pageModuleRepository = provider.GetRequiredService<IPageModuleRepository>();
var moduleRepository = provider.GetRequiredService<IModuleRepository>();
var log = "";
int tenantId = tenantManager.GetTenant().TenantId;
tenantManager.SetAlias(tenantId, primarySiteId); // required by ModuleDefinitionRepository.LoadModuleDefinitions()
var primaryPageModules = pageModuleRepository.GetPageModules(primarySiteId).ToList();
tenantManager.SetAlias(tenantId, secondarySiteId); // required by ModuleDefinitionRepository.LoadModuleDefinitions()
var secondaryPageModules = pageModuleRepository.GetPageModules(secondarySiteId).ToList();
// get pages (ignore personalized)
var primaryPages = pageRepository.GetPages(primarySiteId).Where(item => item.UserId == null);
var secondaryPages = pageRepository.GetPages(secondarySiteId).Where(item => item.UserId == null).ToList();
// iterate through primary pages
foreach (var primaryPage in primaryPages)
{
if (siteGroupMember.SiteGroup.Type == SiteGroupTypes.Synchronization)
{
var page = secondaryPages.FirstOrDefault(item => item.Path == primaryPage.Path);
var secondaryPage = page;
if (secondaryPage == null)
{
secondaryPage = new Page();
secondaryPage.SiteId = secondarySiteId;
}
if (page == null || primaryPage.ModifiedOn > siteGroupMember.SynchronizedOn)
{
// set all properties
secondaryPage.Path = primaryPage.Path;
secondaryPage.Name = primaryPage.Name;
secondaryPage.ParentId = null;
if (primaryPage.ParentId != null)
{
var parentPage = pageRepository.GetPage(primaryPages.First(item => item.PageId == primaryPage.ParentId).Path, secondarySiteId);
if (parentPage != null)
{
secondaryPage.ParentId = parentPage.PageId;
}
}
secondaryPage.Title = primaryPage.Title;
secondaryPage.Order = primaryPage.Order;
secondaryPage.Url = primaryPage.Url;
secondaryPage.ThemeType = primaryPage.ThemeType;
secondaryPage.DefaultContainerType = primaryPage.DefaultContainerType;
secondaryPage.HeadContent = primaryPage.HeadContent;
secondaryPage.BodyContent = primaryPage.BodyContent;
secondaryPage.Icon = primaryPage.Icon;
secondaryPage.IsNavigation = primaryPage.IsNavigation;
secondaryPage.IsClickable = primaryPage.IsClickable;
secondaryPage.UserId = null;
secondaryPage.IsPersonalizable = primaryPage.IsPersonalizable;
secondaryPage.EffectiveDate = primaryPage.EffectiveDate;
secondaryPage.ExpiryDate = primaryPage.ExpiryDate;
secondaryPage.CreatedBy = primaryPage.CreatedBy;
secondaryPage.CreatedOn = primaryPage.CreatedOn;
secondaryPage.ModifiedBy = primaryPage.ModifiedBy;
secondaryPage.ModifiedOn = primaryPage.ModifiedOn;
secondaryPage.DeletedBy = primaryPage.DeletedBy;
secondaryPage.DeletedOn = primaryPage.DeletedOn;
secondaryPage.IsDeleted = primaryPage.IsDeleted;
secondaryPage.PermissionList = SynchronizePermissions(primaryPage.PermissionList, secondarySiteId);
if (page == null)
{
secondaryPage = pageRepository.AddPage(secondaryPage);
log += Log(siteGroupMember, $"Page Added: {CreateLink(siteGroupMember.AliasName + "/" + secondaryPage.Path)}");
}
else
{
secondaryPage = pageRepository.UpdatePage(secondaryPage);
log += Log(siteGroupMember, $"Page Updated: {CreateLink(siteGroupMember.AliasName + "/" + secondaryPage.Path)}");
}
}
if (page != null)
{
secondaryPages.Remove(page);
}
// page settings
log += SynchronizeSettings(settingRepository, siteGroupMember, EntityNames.Page, primaryPage.PageId, secondaryPage.PageId);
// modules
log += SynchronizeModules(provider, settingRepository, pageModuleRepository, moduleRepository, siteGroupMember, primaryPageModules, secondaryPageModules, primaryPage, secondaryPage, secondarySiteId);
}
else // change detection
{
if (primaryPage.ModifiedOn > siteGroupMember.SynchronizedOn)
{
log += Log(siteGroupMember, $"Page Updated: {CreateLink(siteGroupMember.AliasName + "/" + primaryPage.Path)}");
}
// page settings
log += SynchronizeSettings(settingRepository, siteGroupMember, EntityNames.Page, primaryPage.PageId, -1);
// modules
log += SynchronizeModules(provider, settingRepository, pageModuleRepository, moduleRepository, siteGroupMember, primaryPageModules, secondaryPageModules, primaryPage, null, secondarySiteId);
}
}
if (siteGroupMember.SiteGroup.Type == SiteGroupTypes.Synchronization)
{
// remove pages in the secondary site which do not exist in the primary site
foreach (var secondaryPage in secondaryPages)
{
pageRepository.DeletePage(secondaryPage.PageId);
log += Log(siteGroupMember, $"Page Deleted: {CreateLink(siteGroupMember.AliasName + "/" + secondaryPage.Path)}");
}
}
if (siteGroupMember.SiteGroup.Type == SiteGroupTypes.Synchronization && (siteGroupMember.SynchronizedOn == DateTime.MinValue || !string.IsNullOrEmpty(log)))
{
// clear cache for secondary site if any content was Synchronized
var syncManager = provider.GetRequiredService<ISyncManager>();
var alias = new Alias { TenantId = tenantManager.GetTenant().TenantId, SiteId = secondarySiteId };
syncManager.AddSyncEvent(alias, EntityNames.Site, secondarySiteId, SyncEventActions.Refresh);
}
return log;
}
private string SynchronizeModules(IServiceProvider provider, ISettingRepository settingRepository, IPageModuleRepository pageModuleRepository, IModuleRepository moduleRepository, SiteGroupMember siteGroupMember, List<PageModule> primaryPageModules, List<PageModule> secondaryPageModules, Page primaryPage, Page secondaryPage, int secondarySiteId)
{
var log = "";
var removePageModules = secondaryPageModules.Where(item => item.PageId == secondaryPage.PageId).ToList();
// iterate through primary modules on primary page
foreach (var primaryPageModule in primaryPageModules.Where(item => item.PageId == primaryPage.PageId))
{
if (siteGroupMember.SiteGroup.Type == SiteGroupTypes.Synchronization)
{
var pageModule = secondaryPageModules.FirstOrDefault(item => item.PageId == secondaryPage.PageId && item.Module.ModuleDefinitionName == primaryPageModule.Module.ModuleDefinitionName && item.Title.ToLower() == primaryPageModule.Title.ToLower());
var secondaryPageModule = pageModule;
if (secondaryPageModule == null)
{
secondaryPageModule = new PageModule();
secondaryPageModule.PageId = secondaryPage.PageId;
secondaryPageModule.Module = new Module();
secondaryPageModule.Module.SiteId = secondarySiteId;
secondaryPageModule.Module.ModuleDefinitionName = primaryPageModule.Module.ModuleDefinitionName;
}
if (pageModule == null || primaryPageModule.ModifiedOn > siteGroupMember.SynchronizedOn || primaryPageModule.Module.ModifiedOn > siteGroupMember.SynchronizedOn)
{
// set all properties
secondaryPageModule.Title = primaryPageModule.Title;
secondaryPageModule.Pane = primaryPageModule.Pane;
secondaryPageModule.Order = primaryPageModule.Order;
secondaryPageModule.ContainerType = primaryPageModule.ContainerType;
secondaryPageModule.Header = primaryPageModule.Header;
secondaryPageModule.Footer = primaryPageModule.Footer;
secondaryPageModule.IsDeleted = primaryPageModule.IsDeleted;
secondaryPageModule.Module.PermissionList = SynchronizePermissions(primaryPageModule.Module.PermissionList, secondarySiteId);
secondaryPageModule.Module.AllPages = false;
secondaryPageModule.Module.IsDeleted = false;
if (pageModule == null)
{
// check if module exists in site (ie. a shared instance)
var module = secondaryPageModules.FirstOrDefault(item => item.Module.ModuleDefinitionName == primaryPageModule.Module.ModuleDefinitionName && item.Title.ToLower() == primaryPageModule.Title.ToLower())?.Module;
if (module == null)
{
module = moduleRepository.AddModule(secondaryPageModule.Module);
log += Log(siteGroupMember, $"Module Added: {secondaryPageModule.Title} - {CreateLink(siteGroupMember.AliasName + "/" + secondaryPage.Path)}");
}
if (module != null)
{
secondaryPageModule.ModuleId = module.ModuleId;
secondaryPageModule.Module = null; // remove tracking
secondaryPageModule = pageModuleRepository.AddPageModule(secondaryPageModule);
log += Log(siteGroupMember, $"Module Instance Added: {secondaryPageModule.Title} - {CreateLink(siteGroupMember.AliasName + "/" + secondaryPage.Path)}");
secondaryPageModule.Module = module;
}
}
else
{
// update existing module
if (primaryPageModule.Module.ModifiedOn > siteGroupMember.SynchronizedOn)
{
moduleRepository.UpdateModule(secondaryPageModule.Module);
log += Log(siteGroupMember, $"Module Updated: {secondaryPageModule.Title} - {CreateLink(siteGroupMember.AliasName + "/" + secondaryPage.Path)}");
}
if (primaryPageModule.ModifiedOn > siteGroupMember.SynchronizedOn)
{
secondaryPageModule = pageModuleRepository.UpdatePageModule(secondaryPageModule);
log += Log(siteGroupMember, $"Module Instance Updated: {secondaryPageModule.Title} - {CreateLink(siteGroupMember.AliasName + "/" + secondaryPage.Path)}");
}
}
}
if (pageModule != null)
{
removePageModules.Remove(pageModule);
}
// module settings
log += SynchronizeSettings(settingRepository, siteGroupMember, EntityNames.Module, primaryPageModule.ModuleId, secondaryPageModule.ModuleId);
// module content
log += SynchronizeModuleContent(provider, siteGroupMember, primaryPageModule, secondaryPageModule, primaryPage, secondaryPage);
}
else // change detection
{
if (primaryPageModule.Module.ModifiedOn > siteGroupMember.SynchronizedOn)
{
log += Log(siteGroupMember, $"Module Updated: {primaryPageModule.Title} - {CreateLink(siteGroupMember.AliasName + "/" + primaryPage.Path)}");
}
if (primaryPageModule.ModifiedOn > siteGroupMember.SynchronizedOn)
{
log += Log(siteGroupMember, $"Module Instance Updated: {primaryPageModule.Title} - {CreateLink(siteGroupMember.AliasName + "/" + primaryPage.Path)}");
}
// module settings
log += SynchronizeSettings(settingRepository, siteGroupMember, EntityNames.Module, primaryPageModule.ModuleId, -1);
// module content
log += SynchronizeModuleContent(provider, siteGroupMember, primaryPageModule, null, primaryPage, secondaryPage);
}
}
if (siteGroupMember.SiteGroup.Type == SiteGroupTypes.Synchronization)
{
// remove modules on the secondary page which do not exist on the primary page
foreach (var secondaryPageModule in removePageModules)
{
pageModuleRepository.DeletePageModule(secondaryPageModule.PageModuleId);
log += Log(siteGroupMember, $"Module Instance Deleted: {secondaryPageModule.Title} - {CreateLink(siteGroupMember.AliasName + "/" + secondaryPageModule.Page.Path)}");
}
}
return log;
}
private string SynchronizeModuleContent(IServiceProvider provider, SiteGroupMember siteGroupMember, PageModule primaryPageModule, PageModule secondaryPageModule, Page primaryPage, Page secondaryPage)
{
var log = "";
if (primaryPageModule.Module.ModuleDefinition.ServerManagerType != "")
{
Type moduleType = Type.GetType(primaryPageModule.Module.ModuleDefinition.ServerManagerType);
if (moduleType != null && moduleType.GetInterface(nameof(ISynchronizable)) != null)
{
try
{
var moduleObject = ActivatorUtilities.CreateInstance(provider, moduleType);
var moduleContent = ((ISynchronizable)moduleObject).ExtractModule(primaryPageModule.Module, siteGroupMember.SynchronizedOn.Value);
if (!string.IsNullOrEmpty(moduleContent))
{
if (siteGroupMember.SiteGroup.Type == SiteGroupTypes.Synchronization)
{
((ISynchronizable)moduleObject).LoadModule(secondaryPageModule.Module, moduleContent);
log += Log(siteGroupMember, $"Module Content Updated: {secondaryPageModule.Title} - {CreateLink(siteGroupMember.AliasName + "/" + secondaryPage.Path)}");
}
else // change detection
{
log += Log(siteGroupMember, $"Module Content Updated: {primaryPageModule.Title} - {CreateLink(siteGroupMember.AliasName + "/" + primaryPage.Path)}");
}
}
}
catch
{
// error exporting/importing
}
}
}
return log;
}
private List<Permission> SynchronizePermissions(List<Permission> permissionList, int siteId)
{
return permissionList.Select(item => new Permission
{
SiteId = siteId,
PermissionName = item.PermissionName,
RoleName = item.RoleName,
UserId = item.UserId,
IsAuthorized = item.IsAuthorized,
CreatedBy = item.CreatedBy,
CreatedOn = item.CreatedOn,
ModifiedBy = item.ModifiedBy,
ModifiedOn = item.ModifiedOn
}).ToList();
}
private string SynchronizeSettings(ISettingRepository settingRepository, SiteGroupMember siteGroupMember, string entityName, int primaryEntityId, int secondaryEntityId)
{
var log = "";
var updated = false;
var secondarySettings = settingRepository.GetSettings(entityName, secondaryEntityId).ToList();
foreach (var primarySetting in settingRepository.GetSettings(entityName, primaryEntityId))
{
if (siteGroupMember.SiteGroup.Type == SiteGroupTypes.Synchronization)
{
var secondarySetting = secondarySettings.FirstOrDefault(item => item.SettingName == primarySetting.SettingName);
if (secondarySetting == null)
{
secondarySetting = new Setting();
secondarySetting.EntityName = primarySetting.EntityName;
secondarySetting.EntityId = secondaryEntityId;
secondarySetting.SettingName = primarySetting.SettingName;
secondarySetting.SettingValue = primarySetting.SettingValue;
secondarySetting.IsPrivate = primarySetting.IsPrivate;
if (!excludedSettings.Any(item => item.EntityName == secondarySetting.EntityName && item.SettingName == secondarySetting.SettingName))
{
settingRepository.AddSetting(secondarySetting);
updated = true;
}
}
else
{
if (secondarySetting.SettingValue != primarySetting.SettingValue || secondarySetting.IsPrivate != primarySetting.IsPrivate)
{
secondarySetting.SettingValue = primarySetting.SettingValue;
secondarySetting.IsPrivate = primarySetting.IsPrivate;
if (!excludedSettings.Any(item => item.EntityName == secondarySetting.EntityName && item.SettingName == secondarySetting.SettingName))
{
settingRepository.UpdateSetting(secondarySetting);
updated = true;
}
}
secondarySettings.Remove(secondarySetting);
}
}
else // change detection
{
if (primarySetting.ModifiedOn > siteGroupMember.SynchronizedOn && !excludedSettings.Any(item => item.EntityName == primarySetting.EntityName && item.SettingName == primarySetting.SettingName))
{
updated = true;
}
}
}
if (siteGroupMember.SiteGroup.Type == SiteGroupTypes.Synchronization)
{
// any remaining secondary settings need to be deleted
foreach (var secondarySetting in secondarySettings)
{
if (!excludedSettings.Any(item => item.EntityName == secondarySetting.EntityName && item.SettingName == secondarySetting.SettingName))
{
settingRepository.DeleteSetting(secondarySetting.EntityName, secondarySetting.SettingId);
updated = true;
}
}
}
if (updated)
{
log += Log(siteGroupMember, $"{entityName} Settings Updated");
}
return log;
}
private string SendNotifications(IServiceProvider provider, SiteGroupMember siteGroupMember, int siteId, string siteName, string changeLog)
{
var userRoleRepository = provider.GetRequiredService<IUserRoleRepository>();
var notificationRepository = provider.GetRequiredService<INotificationRepository>();
var log = "";
// get administrators for site
var userRoles = userRoleRepository.GetUserRoles(RoleNames.Admin, siteId);
if (userRoles != null && userRoles.Any())
{
foreach (var userRole in userRoles)
{
var notification = new Notification(siteId, userRole.User, $"{siteName} Change Log", changeLog);
notificationRepository.AddNotification(notification);
}
log += Log(siteGroupMember, $"Change Log Sent To Administrators For Secondary Site: {siteName}");
}
else
{
log += Log(siteGroupMember, $"Error Sending Change Log - Secondary Site {siteName} Does Not Have Any Administrators Defined");
}
return log;
}
private string Log(SiteGroupMember siteGroupMember, string content)
{
// not necessary to log initial synchronization
if (siteGroupMember.SynchronizedOn != DateTime.MinValue)
{
return content + "<br />";
}
else
{
return "";
}
}
private string CreateLink(string url)
{
return "<a href=\"" + url + "\" target=\"_new\">" + url + "</a>";
}
}
}

View File

@@ -4,7 +4,6 @@ using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.Options;
using Oqtane.Models;
using Oqtane.Shared;
namespace Oqtane.Infrastructure
@@ -14,6 +13,7 @@ namespace Oqtane.Infrastructure
string GetDefaultCulture();
string[] GetSupportedCultures();
string[] GetInstalledCultures();
string[] GetNeutralCultures();
}
public class LocalizationManager : ILocalizationManager
@@ -41,7 +41,8 @@ namespace Oqtane.Infrastructure
public string[] GetSupportedCultures()
{
return CultureInfo.GetCultures(CultureTypes.AllCultures).Select(item => item.Name).OrderBy(c => c).ToArray();
return CultureInfo.GetCultures(CultureTypes.AllCultures)
.Select(item => item.Name).OrderBy(c => c).ToArray();
}
public string[] GetInstalledCultures()
@@ -49,7 +50,14 @@ namespace Oqtane.Infrastructure
return GetSatelliteAssemblyCultures();
}
// method is static as it is called during startup
public string[] GetNeutralCultures()
{
return CultureInfo.GetCultures(CultureTypes.AllCultures)
.Where(item => item.IsNeutralCulture)
.Select(item => item.Name).OrderBy(c => c).ToArray();
}
// note: method is public and static as it is called during startup
public static string[] GetSatelliteAssemblyCultures()
{
var cultures = new List<string>();

View File

@@ -0,0 +1,164 @@
using System;
using System.Linq;
using System.Net;
using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using Oqtane.Models;
using Oqtane.Modules;
using Oqtane.Repository;
using Oqtane.Shared;
namespace Oqtane.Infrastructure
{
public class GlobalReplaceTask : SiteTaskBase
{
public override string ExecuteTask(IServiceProvider provider, Site site, string parameters)
{
string log = "";
// get services
var siteRepository = provider.GetRequiredService<ISiteRepository>();
var pageRepository = provider.GetRequiredService<IPageRepository>();
var pageModuleRepository = provider.GetRequiredService<IPageModuleRepository>();
var TenantManager = provider.GetRequiredService<ITenantManager>();
var syncManager = provider.GetRequiredService<ISyncManager>();
if (!string.IsNullOrEmpty(parameters))
{
// get parameters
var globalReplace = JsonSerializer.Deserialize<GlobalReplace>(parameters);
var find = globalReplace.Find;
var replace = globalReplace.Replace;
var comparisonType = (globalReplace.CaseSensitive) ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
var refresh = false;
log += $"Replacing: '{find}' With: '{replace}' Case Sensitive: {globalReplace.CaseSensitive}<br />";
// site properties
site = siteRepository.GetSite(site.SiteId);
var changed = false;
if (site.Name != null && site.Name.Contains(find, comparisonType))
{
site.Name = site.Name.Replace(find, replace, comparisonType);
changed = true;
}
if (site.HeadContent != null && site.HeadContent.Contains(find, comparisonType))
{
site.HeadContent = site.HeadContent.Replace(find, replace, comparisonType);
changed = true;
}
if (site.BodyContent != null && site.BodyContent.Contains(find, comparisonType))
{
site.BodyContent = site.BodyContent.Replace(find, replace, comparisonType);
changed = true;
}
if (changed && globalReplace.Site)
{
siteRepository.UpdateSite(site);
log += $"Site Updated<br />";
refresh = true;
}
var pages = pageRepository.GetPages(site.SiteId).ToList();
var pageModules = pageModuleRepository.GetPageModules(site.SiteId).ToList();
// iterate pages
foreach (var page in pages)
{
// page properties
changed = false;
if (page.Name != null && page.Name.Contains(find, comparisonType))
{
page.Name = page.Name.Replace(find, replace, comparisonType);
changed = true;
}
if (page.Title != null && page.Title.Contains(find, comparisonType))
{
page.Title = page.Title.Replace(find, replace, comparisonType);
changed = true;
}
if (page.HeadContent != null && page.HeadContent.Contains(find, comparisonType))
{
page.HeadContent = page.HeadContent.Replace(find, replace, comparisonType);
changed = true;
}
if (page.BodyContent != null && page.BodyContent.Contains(find, comparisonType))
{
page.BodyContent = page.BodyContent.Replace(find, replace, comparisonType);
changed = true;
}
if (changed && globalReplace.Pages)
{
pageRepository.UpdatePage(page);
log += $"Page Updated: /{page.Path}<br />";
refresh = true;
}
foreach (var pageModule in pageModules.Where(item => item.PageId == page.PageId))
{
// module properties
changed = false;
if (pageModule.Title != null && pageModule.Title.Contains(find, comparisonType))
{
pageModule.Title = pageModule.Title.Replace(find, replace, comparisonType);
changed = true;
}
if (pageModule.Header != null && pageModule.Header.Contains(find, comparisonType))
{
pageModule.Header = pageModule.Header.Replace(find, replace, comparisonType);
changed = true;
}
if (pageModule.Footer != null && pageModule.Footer.Contains(find, comparisonType))
{
pageModule.Footer = pageModule.Footer.Replace(find, replace, comparisonType);
changed = true;
}
if (changed && globalReplace.Modules)
{
pageModuleRepository.UpdatePageModule(pageModule);
log += $"Module Updated: {pageModule.Title} Page: /{page.Path}<br />";
refresh = true;
}
// module content
if (pageModule.Module.ModuleDefinition != null && pageModule.Module.ModuleDefinition.ServerManagerType != "")
{
Type moduleType = Type.GetType(pageModule.Module.ModuleDefinition.ServerManagerType);
if (moduleType != null && moduleType.GetInterface(nameof(IPortable)) != null)
{
try
{
var moduleObject = ActivatorUtilities.CreateInstance(provider, moduleType);
var moduleContent = ((IPortable)moduleObject).ExportModule(pageModule.Module);
if (!string.IsNullOrEmpty(moduleContent) && moduleContent.Contains(WebUtility.HtmlEncode(find), comparisonType) && globalReplace.Content)
{
moduleContent = moduleContent.Replace(WebUtility.HtmlEncode(find), WebUtility.HtmlEncode(replace), comparisonType);
((IPortable)moduleObject).ImportModule(pageModule.Module, moduleContent, pageModule.Module.ModuleDefinition.Version);
log += $"Module Content Updated: {pageModule.Title} Page: /{page.Path}<br />";
}
}
catch (Exception ex)
{
log += $"Error Processing Module {pageModule.Module.ModuleDefinition.Name} - {ex.Message}<br />";
}
}
}
}
}
if (refresh)
{
// clear cache
syncManager.AddSyncEvent(TenantManager.GetAlias(), EntityNames.Site, site.SiteId, SyncEventActions.Refresh);
}
}
else
{
log += $"No Criteria Provided<br />";
}
return log;
}
}
}

View File

@@ -0,0 +1,48 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Oqtane.Managers;
using Oqtane.Models;
using Oqtane.Repository;
namespace Oqtane.Infrastructure
{
public class ImportUsersTask : SiteTaskBase
{
public override async Task<string> ExecuteTaskAsync(IServiceProvider provider, Site site, string parameters)
{
string log = "";
if (!string.IsNullOrEmpty(parameters) && parameters.Contains(":"))
{
var fileId = int.Parse(parameters.Split(':')[0]);
var notify = bool.Parse(parameters.Split(':')[1]);
var fileRepository = provider.GetRequiredService<IFileRepository>();
var userManager = provider.GetRequiredService<IUserManager>();
var file = fileRepository.GetFile(fileId);
if (file != null)
{
var filePath = fileRepository.GetFilePath(file);
log += $"Importing Users From {filePath}<br />";
var result = await userManager.ImportUsers(site.SiteId, filePath, notify);
if (result["Success"] == "True")
{
log += $"{result["Users"]} Users Imported<br />";
}
else
{
log += $"User Import Failed<br />";
}
}
else
{
log += $"Import Users FileId {fileId} Does Not Exist<br />";
}
}
return log;
}
}
}

View File

@@ -0,0 +1,19 @@
using System;
using System.Threading.Tasks;
using Oqtane.Models;
namespace Oqtane.Infrastructure
{
public class SiteTaskBase : ISiteTask
{
public virtual string ExecuteTask(IServiceProvider provider, Site site, string parameters)
{
return "";
}
public virtual Task<string> ExecuteTaskAsync(IServiceProvider provider, Site site, string parameters)
{
return Task.FromResult(string.Empty);
}
}
}

View File

@@ -607,6 +607,34 @@ namespace Oqtane.Infrastructure.SiteTemplates
}
}
});
pageTemplates.Add(new PageTemplate
{
Name = "Global Replace",
Parent = "Admin",
Order = 23,
Path = "admin/replace",
Icon = Icons.LoopSquare,
IsNavigation = false,
IsPersonalizable = false,
PermissionList = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.GlobalReplace.Index).ToModuleDefinitionName(), Title = "Global Replace", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
Content = ""
}
}
});
// host pages (order starts at 51)
pageTemplates.Add(new PageTemplate

View File

@@ -49,7 +49,10 @@ namespace Oqtane.Infrastructure.SiteTemplates
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule> {
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "Welcome To Oqtane...", Pane = PaneNames.Default,
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client",
Title = "Welcome To Oqtane...",
Pane = PaneNames.Default,
Order = 1,
PermissionList = new List<Permission> {
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.View, RoleNames.Admin, true),
@@ -60,7 +63,10 @@ namespace Oqtane.Infrastructure.SiteTemplates
"<p><a href=\"https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor\" target=\"_new\">Blazor</a> is a modern front-end web framework based on HTML, CSS, and C# that helps you build web applications faster. Blazor provides a component-based architecture with server-side rendering and full client-side interactivity in a single solution, where you can switch between server-side and client-side rendering modes and even mix them in the same web page. For desktop or mobile scenarios, Blazor Hybrid is part of .NET MAUI and uses a Web View to render components natively on all modern devices.</p>" +
"<p>Blazor is a feature of <a href=\"https://dotnet.microsoft.com/apps/aspnet\" target=\"_new\">ASP.NET</a>, the popular cross platform development framework from Microsoft that provides powerful tools and libraries for building modern software applications.</p>"
},
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "MIT License", Pane = PaneNames.Default,
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client",
Title = "MIT License",
Pane = PaneNames.Default,
Order = 3,
PermissionList = new List<Permission> {
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.View, RoleNames.Admin, true),
@@ -71,7 +77,10 @@ namespace Oqtane.Infrastructure.SiteTemplates
"<p>The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.</p>" +
"<p>THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p>"
},
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "Secure Content", Pane = PaneNames.Default,
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client",
Title = "Secure Content",
Pane = PaneNames.Default,
Order = 5,
PermissionList = new List<Permission> {
new Permission(PermissionNames.View, RoleNames.Registered, true),
new Permission(PermissionNames.View, RoleNames.Admin, true),
@@ -96,7 +105,10 @@ namespace Oqtane.Infrastructure.SiteTemplates
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule> {
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "Secure Content", Pane = PaneNames.Default,
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client",
Title = "Secure Content",
Pane = PaneNames.Default,
Order = 1,
PermissionList = new List<Permission> {
new Permission(PermissionNames.View, RoleNames.Registered, true),
new Permission(PermissionNames.View, RoleNames.Admin, true),
@@ -121,7 +133,10 @@ namespace Oqtane.Infrastructure.SiteTemplates
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule> {
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client", Title = "My Page", Pane = PaneNames.Default,
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.HtmlText, Oqtane.Client",
Title = "My Page",
Pane = PaneNames.Default,
Order = 1,
PermissionList = new List<Permission> {
new Permission(PermissionNames.View, RoleNames.Everyone, true),
new Permission(PermissionNames.View, RoleNames.Admin, true),

View File

@@ -93,6 +93,9 @@ namespace Oqtane.Infrastructure
case "10.0.4":
Upgrade_10_0_4(tenant, scope);
break;
case "10.1.0":
Upgrade_10_1_0(tenant, scope);
break;
}
}
}
@@ -602,6 +605,45 @@ namespace Oqtane.Infrastructure
RemoveAssemblies(tenant, assemblies, "10.0.4");
}
private void Upgrade_10_1_0(Tenant tenant, IServiceScope scope)
{
var pageTemplates = new List<PageTemplate>
{
new PageTemplate
{
Update = false,
Name = "Global Replace",
Parent = "Admin",
Order = 23,
Path = "admin/replace",
Icon = Icons.LoopSquare,
IsNavigation = false,
IsPersonalizable = false,
PermissionList = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.GlobalReplace.Index).ToModuleDefinitionName(), Title = "Global Replace", Pane = PaneNames.Default,
PermissionList = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
},
Content = ""
}
}
}
};
AddPagesToSites(scope, tenant, pageTemplates);
}
private void AddPagesToSites(IServiceScope scope, Tenant tenant, List<PageTemplate> pageTemplates)
{
var tenants = scope.ServiceProvider.GetRequiredService<ITenantManager>();

View File

@@ -0,0 +1,45 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders;
using Oqtane.Databases.Interfaces;
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedAutoPropertyAccessor.Global
namespace Oqtane.Migrations.EntityBuilders
{
public class SiteGroupEntityBuilder : AuditableBaseEntityBuilder<SiteGroupEntityBuilder>
{
private const string _entityTableName = "SiteGroup";
private readonly PrimaryKey<SiteGroupEntityBuilder> _primaryKey = new("PK_SiteGroup", x => x.SiteGroupId);
public SiteGroupEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
{
EntityTableName = _entityTableName;
PrimaryKey = _primaryKey;
}
protected override SiteGroupEntityBuilder BuildTable(ColumnsBuilder table)
{
SiteGroupId = AddAutoIncrementColumn(table, "SiteGroupId");
Name = AddStringColumn(table, "Name", 200);
Type = AddStringColumn(table, "Type", 50);
PrimarySiteId = AddIntegerColumn(table, "PrimarySiteId");
Synchronize = AddBooleanColumn(table, "Synchronize");
AddAuditableColumns(table);
return this;
}
public OperationBuilder<AddColumnOperation> SiteGroupId { get; set; }
public OperationBuilder<AddColumnOperation> Name { get; set; }
public OperationBuilder<AddColumnOperation> Type { get; set; }
public OperationBuilder<AddColumnOperation> PrimarySiteId { get; set; }
public OperationBuilder<AddColumnOperation> Synchronize { get; set; }
}
}

View File

@@ -0,0 +1,46 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders;
using Oqtane.Databases.Interfaces;
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedAutoPropertyAccessor.Global
namespace Oqtane.Migrations.EntityBuilders
{
public class SiteGroupMemberEntityBuilder : AuditableBaseEntityBuilder<SiteGroupMemberEntityBuilder>
{
private const string _entityTableName = "SiteGroupMember";
private readonly PrimaryKey<SiteGroupMemberEntityBuilder> _primaryKey = new("PK_SiteGroupMember", x => x.SiteGroupMemberId);
private readonly ForeignKey<SiteGroupMemberEntityBuilder> _groupForeignKey = new("FK_SiteGroupMember_SiteGroup", x => x.SiteGroupId, "SiteGroup", "SiteGroupId", ReferentialAction.Cascade);
private readonly ForeignKey<SiteGroupMemberEntityBuilder> _siteForeignKey = new("FK_SiteGroupMember_Site", x => x.SiteId, "Site", "SiteId", ReferentialAction.Cascade);
public SiteGroupMemberEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
{
EntityTableName = _entityTableName;
PrimaryKey = _primaryKey;
ForeignKeys.Add(_groupForeignKey);
ForeignKeys.Add(_siteForeignKey);
}
protected override SiteGroupMemberEntityBuilder BuildTable(ColumnsBuilder table)
{
SiteGroupMemberId = AddAutoIncrementColumn(table, "SiteGroupMemberId");
SiteGroupId = AddIntegerColumn(table, "SiteGroupId");
SiteId = AddIntegerColumn(table, "SiteId");
SynchronizedOn = AddDateTimeColumn(table, "SynchronizedOn", true);
AddAuditableColumns(table);
return this;
}
public OperationBuilder<AddColumnOperation> SiteGroupMemberId { get; set; }
public OperationBuilder<AddColumnOperation> SiteGroupId { get; set; }
public OperationBuilder<AddColumnOperation> SiteId { get; set; }
public OperationBuilder<AddColumnOperation> SynchronizedOn { get; set; }
}
}

View File

@@ -0,0 +1,53 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders;
using Oqtane.Databases.Interfaces;
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedAutoPropertyAccessor.Global
namespace Oqtane.Migrations.EntityBuilders
{
public class SiteTaskEntityBuilder : AuditableBaseEntityBuilder<SiteTaskEntityBuilder>
{
private const string _entityTableName = "SiteTask";
private readonly PrimaryKey<SiteTaskEntityBuilder> _primaryKey = new("PK_SiteTask", x => x.SiteTaskId);
private readonly ForeignKey<SiteTaskEntityBuilder> _siteForeignKey = new("FK_SiteTask_Site", x => x.SiteId, "Site", "SiteId", ReferentialAction.Cascade);
public SiteTaskEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
{
EntityTableName = _entityTableName;
PrimaryKey = _primaryKey;
ForeignKeys.Add(_siteForeignKey);
}
protected override SiteTaskEntityBuilder BuildTable(ColumnsBuilder table)
{
SiteTaskId = AddAutoIncrementColumn(table,"SiteTaskId");
SiteId = AddIntegerColumn(table,"SiteId");
Name = AddStringColumn(table, "Name", 200);
Type = AddStringColumn(table, "Type", 200);
Parameters = AddMaxStringColumn(table, "Parameters", true);
IsCompleted = AddBooleanColumn(table, "IsCompleted", true);
Status = AddMaxStringColumn(table, "Status", true);
AddAuditableColumns(table);
return this;
}
public OperationBuilder<AddColumnOperation> SiteTaskId { get; private set; }
public OperationBuilder<AddColumnOperation> SiteId { get; private set; }
public OperationBuilder<AddColumnOperation> Name { get; private set; }
public OperationBuilder<AddColumnOperation> Type { get; private set; }
public OperationBuilder<AddColumnOperation> Parameters { get; private set; }
public OperationBuilder<AddColumnOperation> IsCompleted { get; private set; }
public OperationBuilder<AddColumnOperation> Status { get; private set; }
}
}

View File

@@ -0,0 +1,32 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Oqtane.Databases.Interfaces;
using Oqtane.Migrations.EntityBuilders;
using Oqtane.Repository;
namespace Oqtane.Migrations.Tenant
{
[DbContext(typeof(TenantDBContext))]
[Migration("Tenant.10.01.00.01")]
public class AddSiteGroups : MultiDatabaseMigration
{
public AddSiteGroups(IDatabase database) : base(database)
{
}
protected override void Up(MigrationBuilder migrationBuilder)
{
var siteGroupEntityBuilder = new SiteGroupEntityBuilder(migrationBuilder, ActiveDatabase);
siteGroupEntityBuilder.Create();
var siteGroupMemberEntityBuilder = new SiteGroupMemberEntityBuilder(migrationBuilder, ActiveDatabase);
siteGroupMemberEntityBuilder.Create();
siteGroupMemberEntityBuilder.AddIndex("IX_SiteGroupMember", new[] { "SiteId", "SiteGroupId" }, true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// not implemented
}
}
}

View File

@@ -0,0 +1,33 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Oqtane.Databases.Interfaces;
using Oqtane.Migrations.EntityBuilders;
using Oqtane.Repository;
namespace Oqtane.Migrations.Tenant
{
[DbContext(typeof(TenantDBContext))]
[Migration("Tenant.10.01.00.02")]
public class AddCultureCode : MultiDatabaseMigration
{
public AddCultureCode(IDatabase database) : base(database)
{
}
protected override void Up(MigrationBuilder migrationBuilder)
{
var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase);
siteEntityBuilder.AddStringColumn("CultureCode", 10, true);
siteEntityBuilder.UpdateData("CultureCode", $"'{Shared.Constants.DefaultCulture}'");
var userEntityBuilder = new UserEntityBuilder(migrationBuilder, ActiveDatabase);
userEntityBuilder.AddStringColumn("CultureCode", 10, true);
userEntityBuilder.UpdateData("CultureCode", $"'{Shared.Constants.DefaultCulture}'");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// not implemented
}
}
}

View File

@@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Oqtane.Databases.Interfaces;
using Oqtane.Migrations.EntityBuilders;
using Oqtane.Repository;
namespace Oqtane.Migrations.Tenant
{
[DbContext(typeof(TenantDBContext))]
[Migration("Tenant.10.01.00.03")]
public class RemoveHomePageId : MultiDatabaseMigration
{
public RemoveHomePageId(IDatabase database) : base(database)
{
}
protected override void Up(MigrationBuilder migrationBuilder)
{
var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase);
siteEntityBuilder.DropColumn("HomePageId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// not implemented
}
}
}

View File

@@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Oqtane.Databases.Interfaces;
using Oqtane.Migrations.EntityBuilders;
using Oqtane.Repository;
namespace Oqtane.Migrations.Tenant
{
[DbContext(typeof(TenantDBContext))]
[Migration("Tenant.10.01.00.04")]
public class AddSiteTasks : MultiDatabaseMigration
{
public AddSiteTasks(IDatabase database) : base(database)
{
}
protected override void Up(MigrationBuilder migrationBuilder)
{
var siteTaskEntityBuilder = new SiteTaskEntityBuilder(migrationBuilder, ActiveDatabase);
siteTaskEntityBuilder.Create();
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// not implemented
}
}
}

Some files were not shown because too many files have changed in this diff Show More