@@ -57,6 +57,8 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
services.AddScoped<ICookieConsentService, CookieConsentService>();
|
||||
services.AddScoped<ITimeZoneService, TimeZoneService>();
|
||||
services.AddScoped<IMigrationHistoryService, MigrationHistoryService>();
|
||||
services.AddScoped<ISiteGroupDefinitionService, SiteGroupDefinitionService>();
|
||||
services.AddScoped<ISiteGroupService, SiteGroupService>();
|
||||
services.AddScoped<IOutputCacheService, OutputCacheService>();
|
||||
|
||||
// providers
|
||||
|
||||
@@ -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=""><@SharedLocalizer["Not Specified"]></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);
|
||||
|
||||
@@ -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 ISiteGroupDefinitionService SiteGroupDefinitionService
|
||||
@inject ISiteGroupService SiteGroupService
|
||||
|
||||
@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="-"><@SharedLocalizer["Not Specified"]></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=""><@SharedLocalizer["Not Specified"]></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=""><@SharedLocalizer["Not Specified"]></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;"> </th>
|
||||
<th style="width: 1px;"> </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">
|
||||
@@ -438,6 +387,170 @@
|
||||
</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"><@SharedLocalizer["Not Specified"]></option>
|
||||
@foreach (var alias in _aliases)
|
||||
{
|
||||
<option value="@alias.AliasId">@alias.Name</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="SiteGroups" Heading="Site Groups" ResourceKey="SiteGroups">
|
||||
<div class="container">
|
||||
@if (!_addSiteGroupDefinition)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="group" HelpText="The site groups in this tenant (database)" ResourceKey="SiteGroups">Group: </Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<select id="group" class="form-select" value="@_siteGroupDefinitionId" @onchange="(e => SiteGroupChanged(e))">
|
||||
<option value="-1"><@SharedLocalizer["Not Specified"]></option>
|
||||
@foreach (var siteGroupDefinition in _siteGroupDefinitions)
|
||||
{
|
||||
<option value="@siteGroupDefinition.SiteGroupDefinitionId">@siteGroupDefinition.Name</option>
|
||||
}
|
||||
</select>
|
||||
@if (!_addSiteGroupDefinition)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="AddSiteGroup">@SharedLocalizer["Add"]</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_siteGroupDefinitionId != -1 || _addSiteGroupDefinition)
|
||||
{
|
||||
@if (!_addSiteGroupDefinition)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="member" HelpText="Indicates if the current site is a member of the selected group" ResourceKey="GroupMember">Member? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="member" class="form-select" value="@_member" @onchange="(e => MemberChanged(e))">
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
<option value="Primary">@Localizer["Primary"]</option>
|
||||
<option value="Secondary">@Localizer["Secondary"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_member == "Primary")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="groupname" HelpText="Name of the site group" ResourceKey="GroupName">Group 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="synchronization" HelpText="Specifies if the group supports content synchronization between the primary site and other sites in the group" ResourceKey="Synchronization">Synchronization? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="synchronization" class="form-select" @bind="@_synchronization">
|
||||
<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="localization" HelpText="Specifies if each site that is part of the group contains content which is localized in a different language" ResourceKey="Localization">Localization? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="localization" class="form-select" @bind="@_localization">
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_member == "Secondary")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="synchronize" HelpText="Specifies the synchronization approach from the primary site to the current site" ResourceKey="Approach">Synchronization: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="synchronize" class="form-select" @bind="@_synchronize">
|
||||
<option value="False">@Localizer["Compare"]</option>
|
||||
<option value="True">@Localizer["Update"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="notifyrolename" HelpText="Optionally specifies a role in the current site whose users should be notified of content changes in the primary site" ResourceKey="NotifyRole">Notify Role: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="notifyrolename" class="form-control" @bind="@_notifyRoleName" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<div class="col-sm-3"></div>
|
||||
<div class="col-sm-9">
|
||||
@if ((_siteGroupDefinitionId != -1 || _addSiteGroupDefinition) && _member != "False")
|
||||
{
|
||||
<button type="button" class="btn btn-success me-2" @onclick="SaveSiteGroup">@SharedLocalizer["Save"]</button>
|
||||
}
|
||||
@if ((_siteGroupDefinitionId != -1 && !_addSiteGroupDefinition) && _member != "False")
|
||||
{
|
||||
<ActionDialog Action="Delete" OnClick="@(async () => await DeleteSiteGroup())" ResourceKey="DeleteSiteGroup" Class="btn btn-danger" Header="Delete Site Group" Message="@string.Format(Localizer["Confirm.SiteGroup.Delete", _groupName])" />
|
||||
}
|
||||
@if (_addSiteGroupDefinition)
|
||||
{
|
||||
<button type="button" class="btn btn-secondary" @onclick="CancelSiteGroup">@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">
|
||||
@@ -464,6 +577,10 @@
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveSite">@SharedLocalizer["Save"]</button>
|
||||
<ActionDialog Header="Delete Site" Message="@Localizer["Confirm.DeleteSite"]" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteSite())" ResourceKey="DeleteSite" />
|
||||
@if (_siteGroupDefinitions.Any(item => item.PrimarySiteId == PageState.Site.SiteId && item.Synchronization))
|
||||
{
|
||||
<button type="button" class="btn btn-primary ms-2" @onclick="SynchronizeSite">@Localizer["Synchronize"]</button>
|
||||
}
|
||||
<br />
|
||||
<br />
|
||||
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
|
||||
@@ -478,10 +595,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 = "";
|
||||
@@ -531,6 +649,7 @@
|
||||
private int _aliasid = -1;
|
||||
private string _aliasname;
|
||||
private string _defaultalias;
|
||||
private bool _addAlias = false;
|
||||
|
||||
private string _rendermode = RenderModes.Interactive;
|
||||
private string _enhancednavigation = "True";
|
||||
@@ -538,6 +657,16 @@
|
||||
private string _prerender = "True";
|
||||
private string _hybrid = "False";
|
||||
|
||||
private List<Models.SiteGroupDefinition> _siteGroupDefinitions = new List<Models.SiteGroupDefinition>();
|
||||
private int _siteGroupDefinitionId = -1;
|
||||
private string _groupName = string.Empty;
|
||||
private string _member = "Primary";
|
||||
private string _synchronization = "True";
|
||||
private string _synchronize = "True";
|
||||
private string _notifyRoleName = RoleNames.Admin;
|
||||
private string _localization = "False";
|
||||
private bool _addSiteGroupDefinition = false;
|
||||
|
||||
private string _tenant = string.Empty;
|
||||
private string _database = string.Empty;
|
||||
private string _connectionstring = string.Empty;
|
||||
@@ -564,16 +693,14 @@
|
||||
if (site != null)
|
||||
{
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
_cultures = await LocalizationService.GetCulturesAsync(false);
|
||||
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 +779,7 @@
|
||||
}
|
||||
|
||||
// aliases
|
||||
await GetAliases();
|
||||
await LoadAliases();
|
||||
|
||||
// hosting model
|
||||
_rendermode = site.RenderMode;
|
||||
@@ -676,6 +803,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
// site groups
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
await LoadSiteGroups();
|
||||
}
|
||||
|
||||
// audit
|
||||
_createdby = site.CreatedBy;
|
||||
_createdon = site.CreatedOn;
|
||||
@@ -743,7 +876,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 +1125,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 +1228,181 @@
|
||||
await CacheService.EvictByTag(Constants.SitemapOutputCacheTag);
|
||||
AddModuleMessage(Localizer["Success.SiteMap.CacheEvicted"], MessageType.Success);
|
||||
}
|
||||
|
||||
private async Task LoadSiteGroups()
|
||||
{
|
||||
_siteGroupDefinitions = await SiteGroupDefinitionService.GetSiteGroupDefinitionsAsync();
|
||||
_siteGroupDefinitionId = -1;
|
||||
_addSiteGroupDefinition = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async void SiteGroupChanged(ChangeEventArgs e)
|
||||
{
|
||||
_siteGroupDefinitionId = int.Parse(e.Value.ToString());
|
||||
if (_siteGroupDefinitionId != -1)
|
||||
{
|
||||
var group = _siteGroupDefinitions.FirstOrDefault(item => item.SiteGroupDefinitionId == _siteGroupDefinitionId);
|
||||
if (group != null)
|
||||
{
|
||||
_groupName = group.Name;
|
||||
_member = (group.PrimarySiteId == PageState.Site.SiteId) ? "Primary" : "Secondary";
|
||||
_synchronization = group.Synchronization.ToString();
|
||||
_localization = group.Localization.ToString();
|
||||
}
|
||||
|
||||
var siteGroup = await SiteGroupService.GetSiteGroupAsync(PageState.Site.SiteId, _siteGroupDefinitionId);
|
||||
if (siteGroup != null)
|
||||
{
|
||||
_synchronize = siteGroup.Synchronize.ToString();
|
||||
_notifyRoleName = siteGroup.NotifyRoleName;
|
||||
}
|
||||
else
|
||||
{
|
||||
_member = "False";
|
||||
_synchronize = "True";
|
||||
_notifyRoleName = RoleNames.Admin;
|
||||
}
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async void MemberChanged(ChangeEventArgs e)
|
||||
{
|
||||
_member = e.Value.ToString();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task AddSiteGroup()
|
||||
{
|
||||
_groupName = "";
|
||||
_member = "Primary";
|
||||
_addSiteGroupDefinition = true;
|
||||
}
|
||||
|
||||
private async Task SaveSiteGroup()
|
||||
{
|
||||
SiteGroupDefinition siteGroupDefinition = null;
|
||||
|
||||
if (_siteGroupDefinitionId == -1)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_groupName))
|
||||
{
|
||||
siteGroupDefinition = new Models.SiteGroupDefinition
|
||||
{
|
||||
Name = _groupName,
|
||||
PrimarySiteId = PageState.Site.SiteId,
|
||||
Synchronization = bool.Parse(_synchronization),
|
||||
Localization = bool.Parse(_localization),
|
||||
Synchronize = false
|
||||
};
|
||||
siteGroupDefinition = await SiteGroupDefinitionService.AddSiteGroupDefinitionAsync(siteGroupDefinition);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
siteGroupDefinition = _siteGroupDefinitions.FirstOrDefault(item => item.SiteGroupDefinitionId == _siteGroupDefinitionId);
|
||||
if (siteGroupDefinition != null && !string.IsNullOrEmpty(_groupName))
|
||||
{
|
||||
if (_member == "False")
|
||||
{
|
||||
var siteGroup = await SiteGroupService.GetSiteGroupAsync(PageState.Site.SiteId, siteGroupDefinition.SiteGroupDefinitionId);
|
||||
if (siteGroup != null)
|
||||
{
|
||||
await SiteGroupService.DeleteSiteGroupAsync(siteGroup.SiteGroupId);
|
||||
}
|
||||
siteGroupDefinition = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
siteGroupDefinition.Name = _groupName;
|
||||
siteGroupDefinition.PrimarySiteId = (_member == "Primary") ? PageState.Site.SiteId : siteGroupDefinition.PrimarySiteId;
|
||||
siteGroupDefinition.Synchronization = bool.Parse(_synchronization);
|
||||
siteGroupDefinition.Localization = bool.Parse(_localization);
|
||||
siteGroupDefinition = await SiteGroupDefinitionService.UpdateSiteGroupDefinitionAsync(siteGroupDefinition);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
siteGroupDefinition = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (siteGroupDefinition != null)
|
||||
{
|
||||
var siteGroup = await SiteGroupService.GetSiteGroupAsync(PageState.Site.SiteId, siteGroupDefinition.SiteGroupDefinitionId);
|
||||
if (siteGroup == null)
|
||||
{
|
||||
siteGroup = new SiteGroup
|
||||
{
|
||||
SiteGroupDefinitionId = siteGroupDefinition.SiteGroupDefinitionId,
|
||||
SiteId = PageState.Site.SiteId,
|
||||
Synchronize = bool.Parse(_synchronize),
|
||||
NotifyRoleName= _notifyRoleName
|
||||
};
|
||||
await SiteGroupService.AddSiteGroupAsync(siteGroup);
|
||||
}
|
||||
else
|
||||
{
|
||||
siteGroup.Synchronize = bool.Parse(_synchronize);
|
||||
siteGroup.NotifyRoleName = _notifyRoleName;
|
||||
await SiteGroupService.UpdateSiteGroupAsync(siteGroup);
|
||||
}
|
||||
|
||||
await LoadSiteGroups();
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Required.GroupName"], MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CancelSiteGroup()
|
||||
{
|
||||
_groupName = "";
|
||||
await LoadSiteGroups();
|
||||
}
|
||||
|
||||
private async Task DeleteSiteGroup()
|
||||
{
|
||||
if (_siteGroupDefinitionId != -1)
|
||||
{
|
||||
var siteGroup = await SiteGroupService.GetSiteGroupAsync(PageState.Site.SiteId, _siteGroupDefinitionId);
|
||||
if (siteGroup != null)
|
||||
{
|
||||
await SiteGroupService.DeleteSiteGroupAsync(siteGroup.SiteGroupDefinitionId);
|
||||
}
|
||||
|
||||
var siteGroups = await SiteGroupService.GetSiteGroupsAsync(-1, _siteGroupDefinitionId);
|
||||
if (!siteGroups.Any())
|
||||
{
|
||||
await SiteGroupDefinitionService.DeleteSiteGroupDefinitionAsync(_siteGroupDefinitionId);
|
||||
}
|
||||
|
||||
await LoadSiteGroups();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SynchronizeSite()
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
// mark secondary sites for synchronization
|
||||
foreach (var group in _siteGroupDefinitions.Where(item => item.PrimarySiteId == PageState.Site.SiteId && item.Synchronization))
|
||||
{
|
||||
group.Synchronize = true;
|
||||
await SiteGroupDefinitionService.UpdateSiteGroupDefinitionAsync(group);
|
||||
}
|
||||
|
||||
AddModuleMessage(Localizer["Message.Site.Synchronize"], MessageType.Success);
|
||||
await ScrollToPageTop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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="-"><@Localizer["Theme.Select"]></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="-"><@Localizer["Container.Select"]></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();
|
||||
|
||||
@@ -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=""><@SharedLocalizer["Not Specified"]></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)
|
||||
{
|
||||
|
||||
@@ -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=""><@SharedLocalizer["Not Specified"]></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);
|
||||
|
||||
|
||||
@@ -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=""><@SharedLocalizer["Not Specified"]></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">
|
||||
@@ -224,13 +237,16 @@
|
||||
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;
|
||||
@@ -270,12 +286,15 @@
|
||||
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();
|
||||
_lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", UtcToLocal(user.LastLoginOn));
|
||||
_lastipaddress = user.LastIPAddress;
|
||||
@@ -344,6 +363,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));
|
||||
|
||||
@@ -7,6 +7,20 @@ 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> GetHtmlTextAsync(int htmlTextId, 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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -319,13 +319,13 @@
|
||||
<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>
|
||||
@@ -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="SiteGroups.Text" xml:space="preserve">
|
||||
<value>Group:</value>
|
||||
</data>
|
||||
<data name="SiteGroups.HelpText" xml:space="preserve">
|
||||
<value>The site groups in this tenant (database)</value>
|
||||
</data>
|
||||
<data name="GroupMember.Text" xml:space="preserve">
|
||||
<value>Member?</value>
|
||||
</data>
|
||||
<data name="GroupMember.HelpText" xml:space="preserve">
|
||||
<value>Indicates if the current site is a member of the selected 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="Synchronization.Text" xml:space="preserve">
|
||||
<value>Synchronization?</value>
|
||||
</data>
|
||||
<data name="Synchronization.HelpText" xml:space="preserve">
|
||||
<value>Specifies if the group supports content synchronization between the primary site and other sites in the group</value>
|
||||
</data>
|
||||
<data name="Localization.Text" xml:space="preserve">
|
||||
<value>Localization?</value>
|
||||
</data>
|
||||
<data name="Localization.HelpText" xml:space="preserve">
|
||||
<value>Specifies if each site that is part of the group contains content which is localized in a different language</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="Approach.Text" xml:space="preserve">
|
||||
<value>Synchronization:</value>
|
||||
</data>
|
||||
<data name="Approach.HelpText" xml:space="preserve">
|
||||
<value>Specifies the synchronization approach from the primary site to the current site</value>
|
||||
</data>
|
||||
<data name="NotifyRole.Text" xml:space="preserve">
|
||||
<value>Notify Role:</value>
|
||||
</data>
|
||||
<data name="NotifyRole.HelpText" xml:space="preserve">
|
||||
<value>Optionally specifies a role in the current site whose users should be notified of content changes in the primary site</value>
|
||||
</data>
|
||||
<data name="DeleteSiteGroup.Header" xml:space="preserve">
|
||||
<value>Delete Site Group</value>
|
||||
</data>
|
||||
<data name="Confirm.SiteGroup.Delete" xml:space="preserve">
|
||||
<value>Are You Sure You Wish To Delete {0}?</value>
|
||||
</data>
|
||||
<data name="Message.Required.GroupName" xml:space="preserve">
|
||||
<value>Group Name Is Required</value>
|
||||
</data>
|
||||
<data name="Message.Site.Synchronize" xml:space="preserve">
|
||||
<value>Site Submitted For Synchronization</value>
|
||||
</data>
|
||||
<data name="Synchronize" xml:space="preserve">
|
||||
<value>Synchronize</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -133,7 +133,7 @@
|
||||
<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>
|
||||
<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="DefaultContainer.HelpText" xml:space="preserve">
|
||||
<value>Select the default container for the site</value>
|
||||
@@ -142,7 +142,7 @@
|
||||
<value>Database: </value>
|
||||
</data>
|
||||
<data name="Aliases.Text" xml:space="preserve">
|
||||
<value>Urls: </value>
|
||||
<value>Url: </value>
|
||||
</data>
|
||||
<data name="DefaultTheme.Text" xml:space="preserve">
|
||||
<value>Default Theme: </value>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Oqtane.Documentation;
|
||||
@@ -26,6 +27,9 @@ 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
83
Oqtane.Client/Services/SiteGroupDefinitionService.cs
Normal file
83
Oqtane.Client/Services/SiteGroupDefinitionService.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
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 ISiteGroupDefinitionService
|
||||
{
|
||||
/// <summary>
|
||||
/// Get all <see cref="SiteGroupDefinition"/>s
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<List<SiteGroupDefinition>> GetSiteGroupDefinitionsAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Get one specific <see cref="SiteGroupDefinition"/>
|
||||
/// </summary>
|
||||
/// <param name="siteGroupDefinitionId">ID-reference of a <see cref="SiteGroupDefinition"/></param>
|
||||
/// <returns></returns>
|
||||
Task<SiteGroupDefinition> GetSiteGroupDefinitionAsync(int siteGroupDefinitionId);
|
||||
|
||||
/// <summary>
|
||||
/// Add / save a new <see cref="SiteGroupDefinition"/> to the database.
|
||||
/// </summary>
|
||||
/// <param name="group"></param>
|
||||
/// <returns></returns>
|
||||
Task<SiteGroupDefinition> AddSiteGroupDefinitionAsync(SiteGroupDefinition siteGroupDefinition);
|
||||
|
||||
/// <summary>
|
||||
/// Update a <see cref="SiteGroupDefinition"/> in the database.
|
||||
/// </summary>
|
||||
/// <param name="group"></param>
|
||||
/// <returns></returns>
|
||||
Task<SiteGroupDefinition> UpdateSiteGroupDefinitionAsync(SiteGroupDefinition siteGroupDefinition);
|
||||
|
||||
/// <summary>
|
||||
/// Delete a <see cref="SiteGroupDefinition"/> in the database.
|
||||
/// </summary>
|
||||
/// <param name="siteGroupDefinitionId">ID-reference of a <see cref="SiteGroupDefinition"/></param>
|
||||
/// <returns></returns>
|
||||
Task DeleteSiteGroupDefinitionAsync(int siteGroupDefinitionId);
|
||||
}
|
||||
|
||||
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
|
||||
public class SiteGroupDefinitionService : ServiceBase, ISiteGroupDefinitionService
|
||||
{
|
||||
public SiteGroupDefinitionService(HttpClient http, SiteState siteState) : base(http, siteState) { }
|
||||
|
||||
private string Apiurl => CreateApiUrl("SiteGroupDefinition");
|
||||
|
||||
public async Task<List<SiteGroupDefinition>> GetSiteGroupDefinitionsAsync()
|
||||
{
|
||||
return await GetJsonAsync<List<SiteGroupDefinition>>($"{Apiurl}", Enumerable.Empty<SiteGroupDefinition>().ToList());
|
||||
}
|
||||
|
||||
public async Task<SiteGroupDefinition> GetSiteGroupDefinitionAsync(int siteGroupDefinitionId)
|
||||
{
|
||||
return await GetJsonAsync<SiteGroupDefinition>($"{Apiurl}/{siteGroupDefinitionId}");
|
||||
}
|
||||
|
||||
public async Task<SiteGroupDefinition> AddSiteGroupDefinitionAsync(SiteGroupDefinition siteGroupDefinition)
|
||||
{
|
||||
return await PostJsonAsync<SiteGroupDefinition>(Apiurl, siteGroupDefinition);
|
||||
}
|
||||
|
||||
public async Task<SiteGroupDefinition> UpdateSiteGroupDefinitionAsync(SiteGroupDefinition siteGroupDefinition)
|
||||
{
|
||||
return await PutJsonAsync<SiteGroupDefinition>($"{Apiurl}/{siteGroupDefinition.SiteGroupDefinitionId}", siteGroupDefinition);
|
||||
}
|
||||
|
||||
public async Task DeleteSiteGroupDefinitionAsync(int siteGroupDefinitionId)
|
||||
{
|
||||
await DeleteAsync($"{Apiurl}/{siteGroupDefinitionId}");
|
||||
}
|
||||
}
|
||||
}
|
||||
104
Oqtane.Client/Services/SiteGroupService.cs
Normal file
104
Oqtane.Client/Services/SiteGroupService.cs
Normal 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 ISiteGroupService
|
||||
{
|
||||
/// <summary>
|
||||
/// Get all <see cref="SiteGroup"/>s
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<List<SiteGroup>> GetSiteGroupsAsync(int siteId, int siteGroupDefinitionId);
|
||||
|
||||
/// <summary>
|
||||
/// Get one specific <see cref="SiteGroup"/>
|
||||
/// </summary>
|
||||
/// <param name="siteSiteGroupDefinitionId">ID-reference of a <see cref="SiteGroup"/></param>
|
||||
/// <returns></returns>
|
||||
Task<SiteGroup> GetSiteGroupAsync(int siteSiteGroupDefinitionId);
|
||||
|
||||
/// <summary>
|
||||
/// Get one specific <see cref="SiteGroup"/>
|
||||
/// </summary>
|
||||
/// <param name="siteId">ID-reference of a <see cref="Site"/></param>
|
||||
/// <param name="siteGroupDefinitionId">ID-reference of a <see cref="SiteGroupDefinition"/></param>
|
||||
/// <returns></returns>
|
||||
Task<SiteGroup> GetSiteGroupAsync(int siteId, int siteGroupDefinitionId);
|
||||
|
||||
/// <summary>
|
||||
/// Add / save a new <see cref="SiteGroup"/> to the database.
|
||||
/// </summary>
|
||||
/// <param name="siteGroup"></param>
|
||||
/// <returns></returns>
|
||||
Task<SiteGroup> AddSiteGroupAsync(SiteGroup siteGroup);
|
||||
|
||||
/// <summary>
|
||||
/// Update a <see cref="SiteGroup"/> in the database.
|
||||
/// </summary>
|
||||
/// <param name="siteGroup"></param>
|
||||
/// <returns></returns>
|
||||
Task<SiteGroup> UpdateSiteGroupAsync(SiteGroup siteGroup);
|
||||
|
||||
/// <summary>
|
||||
/// Delete a <see cref="SiteGroup"/> in the database.
|
||||
/// </summary>
|
||||
/// <param name="siteSiteGroupDefinitionId">ID-reference of a <see cref="SiteGroup"/></param>
|
||||
/// <returns></returns>
|
||||
Task DeleteSiteGroupAsync(int siteSiteGroupDefinitionId);
|
||||
}
|
||||
|
||||
[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(int siteId, int siteGroupDefinitionId)
|
||||
{
|
||||
return await GetJsonAsync<List<SiteGroup>>($"{Apiurl}?siteid={siteId}&groupid={siteGroupDefinitionId}", Enumerable.Empty<SiteGroup>().ToList());
|
||||
}
|
||||
|
||||
public async Task<SiteGroup> GetSiteGroupAsync(int siteSiteGroupDefinitionId)
|
||||
{
|
||||
return await GetJsonAsync<SiteGroup>($"{Apiurl}/{siteSiteGroupDefinitionId}");
|
||||
}
|
||||
|
||||
public async Task<SiteGroup> GetSiteGroupAsync(int siteId, int siteGroupDefinitionId)
|
||||
{
|
||||
var siteGroups = await GetSiteGroupsAsync(siteId, siteGroupDefinitionId);
|
||||
if (siteGroups != null && siteGroups.Count > 0)
|
||||
{
|
||||
return siteGroups[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
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.SiteGroupDefinitionId}", siteGroup);
|
||||
}
|
||||
|
||||
public async Task DeleteSiteGroupAsync(int siteSiteGroupDefinitionId)
|
||||
{
|
||||
await DeleteAsync($"{Apiurl}/{siteSiteGroupDefinitionId}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,6 @@
|
||||
@inject IPageService PageService
|
||||
@inject ISettingService SettingService
|
||||
|
||||
@if (ShowLanguageSwitcher)
|
||||
{
|
||||
<LanguageSwitcher ButtonClass="@ButtonClass" DropdownAlignment="@LanguageDropdownAlignment" />
|
||||
}
|
||||
|
||||
@if (ShowEditMode && (_showEditMode || (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered))))
|
||||
{
|
||||
<form method="post" class="app-form-inline" @formname="EditModeForm" @onsubmit="@(async () => await ToggleEditMode(PageState.EditMode))" data-enhance>
|
||||
@@ -53,9 +48,11 @@
|
||||
[Parameter]
|
||||
public string BodyClass { get; set; } = "offcanvas-body overflow-auto";
|
||||
|
||||
// deprecated in 10.1.0 - UI culture is set in user's profile
|
||||
[Parameter]
|
||||
public bool ShowLanguageSwitcher { get; set; } = true;
|
||||
|
||||
// deprecated in 10.1.0 - UI culture is set in user's profile
|
||||
[Parameter]
|
||||
public string LanguageDropdownAlignment { get; set; } = string.Empty; // Empty or Left or Right
|
||||
|
||||
|
||||
37
Oqtane.Client/Themes/Controls/Theme/LanguageSelector.razor
Normal file
37
Oqtane.Client/Themes/Controls/Theme/LanguageSelector.razor
Normal file
@@ -0,0 +1,37 @@
|
||||
@using System.Globalization
|
||||
@using Oqtane.Models
|
||||
@using System.Linq
|
||||
@namespace Oqtane.Themes.Controls
|
||||
@inherits ThemeControlBase
|
||||
@inject ILocalizationService LocalizationService
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@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">
|
||||
@foreach (var language in PageState.Site.Languages)
|
||||
{
|
||||
<a class="dropdown-item" href="@(PageState.Alias.Protocol + language.AliasName)">@language.Name</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code{
|
||||
private string MenuAlignment = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public string DropdownAlignment { get; set; } = string.Empty; // Empty or Left or Right
|
||||
|
||||
[Parameter]
|
||||
public string ButtonClass { get; set; } = "btn-outline-secondary";
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
MenuAlignment = DropdownAlignment.ToLower() == "right" ? "dropdown-menu-end" : string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
<Search CssClass="me-3 text-center bg-primary" />
|
||||
<UserProfile ShowRegister="@_register" />
|
||||
<Login ShowLogin="@_login" />
|
||||
<LanguageSelector />
|
||||
<ControlPanel LanguageDropdownAlignment="right" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -157,15 +157,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)
|
||||
{
|
||||
|
||||
@@ -3,8 +3,10 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Oqtane.Extensions;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Repository;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Controllers
|
||||
@@ -13,10 +15,16 @@ namespace Oqtane.Controllers
|
||||
public class LocalizationController : Controller
|
||||
{
|
||||
private readonly ILocalizationManager _localizationManager;
|
||||
private readonly ISiteRepository _siteRepository;
|
||||
private readonly ISiteGroupRepository _siteGroupRepository;
|
||||
private readonly IAliasRepository _aliasRepository;
|
||||
|
||||
public LocalizationController(ILocalizationManager localizationManager)
|
||||
public LocalizationController(ILocalizationManager localizationManager, ISiteRepository siteRepository, ISiteGroupRepository siteGroupRepository, IAliasRepository aliasRepository)
|
||||
{
|
||||
_localizationManager = localizationManager;
|
||||
_siteRepository = siteRepository;
|
||||
_siteGroupRepository = siteGroupRepository;
|
||||
_aliasRepository = aliasRepository;
|
||||
}
|
||||
|
||||
// GET: api/localization
|
||||
@@ -32,13 +40,20 @@ namespace Oqtane.Controllers
|
||||
{
|
||||
culturecodes = _localizationManager.GetSupportedCultures();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
123
Oqtane.Server/Controllers/SiteGroupController.cs
Normal file
123
Oqtane.Server/Controllers/SiteGroupController.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
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 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&groupid=y
|
||||
[HttpGet]
|
||||
[Authorize(Roles = RoleNames.Host)]
|
||||
public IEnumerable<SiteGroup> Get(string siteid, string groupid)
|
||||
{
|
||||
if (int.TryParse(siteid, out int SiteId) && int.TryParse(groupid, out int SiteGroupDefinitionId))
|
||||
{
|
||||
return _siteGroupRepository.GetSiteGroups(SiteId, SiteGroupDefinitionId).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Site Group Get Attempt for SiteId {SiteId} And SiteGroupDefinitionId {SiteGroupDefinitionId}", siteid, groupid);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// GET api/<controller>/5
|
||||
[HttpGet("{id}")]
|
||||
[Authorize(Roles = RoleNames.Host)]
|
||||
public SiteGroup Get(int id)
|
||||
{
|
||||
var siteGroup = _siteGroupRepository.GetSiteGroup(id);
|
||||
if (siteGroup != null)
|
||||
{
|
||||
return siteGroup;
|
||||
}
|
||||
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.SiteGroupDefinitionId, SyncEventActions.Create);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Site Group Added {SiteGroup}", siteGroup);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Site Group Post Attempt {SiteGroup}", siteGroup);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
siteGroup = null;
|
||||
}
|
||||
return siteGroup;
|
||||
}
|
||||
|
||||
// PUT api/<controller>/5
|
||||
[HttpPut("{id}")]
|
||||
[Authorize(Roles = RoleNames.Host)]
|
||||
public SiteGroup Put(int id, [FromBody] SiteGroup siteGroup)
|
||||
{
|
||||
if (ModelState.IsValid && siteGroup.SiteGroupDefinitionId == id && _siteGroupRepository.GetSiteGroup(siteGroup.SiteGroupDefinitionId, false) != null)
|
||||
{
|
||||
siteGroup = _siteGroupRepository.UpdateSiteGroup(siteGroup);
|
||||
_syncManager.AddSyncEvent(_alias, EntityNames.SiteGroup, siteGroup.SiteGroupDefinitionId, SyncEventActions.Update);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Site Group Updated {SiteGroup}", siteGroup);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Site Group Put Attempt {SiteGroup}", 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.SiteGroupDefinitionId, SyncEventActions.Delete);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Site Group Deleted {SiteGroupDefinitionId}", id);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Site Group Delete Attempt {SiteGroupDefinitionId}", id);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
114
Oqtane.Server/Controllers/SiteGroupDefinitionController.cs
Normal file
114
Oqtane.Server/Controllers/SiteGroupDefinitionController.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Oqtane.Enums;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Shared;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Repository;
|
||||
using System.Net;
|
||||
using System.Linq;
|
||||
|
||||
namespace Oqtane.Controllers
|
||||
{
|
||||
[Route(ControllerRoutes.ApiRoute)]
|
||||
public class SiteGroupDefinitionController : Controller
|
||||
{
|
||||
private readonly ISiteGroupDefinitionRepository _siteGroupDefinitionRepository;
|
||||
private readonly ISyncManager _syncManager;
|
||||
private readonly ILogManager _logger;
|
||||
private readonly Alias _alias;
|
||||
|
||||
public SiteGroupDefinitionController(ISiteGroupDefinitionRepository siteGroupDefinitionRepository, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager)
|
||||
{
|
||||
_siteGroupDefinitionRepository = siteGroupDefinitionRepository;
|
||||
_syncManager = syncManager;
|
||||
_logger = logger;
|
||||
_alias = tenantManager.GetAlias();
|
||||
}
|
||||
|
||||
// GET: api/<controller>
|
||||
[HttpGet]
|
||||
[Authorize(Roles = RoleNames.Host)]
|
||||
public IEnumerable<SiteGroupDefinition> Get()
|
||||
{
|
||||
return _siteGroupDefinitionRepository.GetSiteGroupDefinitions().ToList();
|
||||
}
|
||||
|
||||
// GET api/<controller>/5
|
||||
[HttpGet("{id}")]
|
||||
[Authorize(Roles = RoleNames.Host)]
|
||||
public SiteGroupDefinition Get(int id)
|
||||
{
|
||||
var group = _siteGroupDefinitionRepository.GetSiteGroupDefinition(id);
|
||||
if (group != null)
|
||||
{
|
||||
return group;
|
||||
}
|
||||
else
|
||||
{
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// POST api/<controller>
|
||||
[HttpPost]
|
||||
[Authorize(Roles = RoleNames.Host)]
|
||||
public SiteGroupDefinition Post([FromBody] SiteGroupDefinition siteGroupDefinition)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
siteGroupDefinition = _siteGroupDefinitionRepository.AddSiteGroupDefinition(siteGroupDefinition);
|
||||
_syncManager.AddSyncEvent(_alias, EntityNames.SiteGroupDefinition, siteGroupDefinition.SiteGroupDefinitionId, SyncEventActions.Create);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Site Group Definition Added {Group}", siteGroupDefinition);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Site Group Definition Post Attempt {Group}", siteGroupDefinition);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
siteGroupDefinition = null;
|
||||
}
|
||||
return siteGroupDefinition;
|
||||
}
|
||||
|
||||
// PUT api/<controller>/5
|
||||
[HttpPut("{id}")]
|
||||
[Authorize(Roles = RoleNames.Host)]
|
||||
public SiteGroupDefinition Put(int id, [FromBody] SiteGroupDefinition siteGroupDefinition)
|
||||
{
|
||||
if (ModelState.IsValid && siteGroupDefinition.SiteGroupDefinitionId == id && _siteGroupDefinitionRepository.GetSiteGroupDefinition(siteGroupDefinition.SiteGroupDefinitionId, false) != null)
|
||||
{
|
||||
siteGroupDefinition = _siteGroupDefinitionRepository.UpdateSiteGroupDefinition(siteGroupDefinition);
|
||||
_syncManager.AddSyncEvent(_alias, EntityNames.SiteGroupDefinition, siteGroupDefinition.SiteGroupDefinitionId, SyncEventActions.Update);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Site Group Definition Updated {Group}", siteGroupDefinition);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Site Group Definition Put Attempt {Group}", siteGroupDefinition);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
siteGroupDefinition = null;
|
||||
}
|
||||
return siteGroupDefinition;
|
||||
}
|
||||
|
||||
// DELETE api/<controller>/5
|
||||
[HttpDelete("{id}")]
|
||||
[Authorize(Roles = RoleNames.Host)]
|
||||
public void Delete(int id)
|
||||
{
|
||||
var siteGroupDefinition = _siteGroupDefinitionRepository.GetSiteGroupDefinition(id);
|
||||
if (siteGroupDefinition != null)
|
||||
{
|
||||
_siteGroupDefinitionRepository.DeleteSiteGroupDefinition(id);
|
||||
_syncManager.AddSyncEvent(_alias, EntityNames.SiteGroupDefinition, siteGroupDefinition.SiteGroupDefinitionId, SyncEventActions.Delete);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Site Group Definition Deleted {siteGroupDefinitionId}", id);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Site Group Definition Delete Attempt {siteGroupDefinitionId}", id);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,6 +233,8 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
services.AddScoped<ICookieConsentService, ServerCookieConsentService>();
|
||||
services.AddScoped<ITimeZoneService, TimeZoneService>();
|
||||
services.AddScoped<IMigrationHistoryService, MigrationHistoryService>();
|
||||
services.AddScoped<ISiteGroupDefinitionService, SiteGroupDefinitionService>();
|
||||
services.AddScoped<ISiteGroupService, SiteGroupService>();
|
||||
|
||||
// providers
|
||||
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.QuillJSTextEditor>();
|
||||
@@ -282,6 +284,8 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
services.AddTransient<IUrlMappingRepository, UrlMappingRepository>();
|
||||
services.AddTransient<ISearchContentRepository, SearchContentRepository>();
|
||||
services.AddTransient<IMigrationHistoryRepository, MigrationHistoryRepository>();
|
||||
services.AddTransient<ISiteGroupDefinitionRepository, SiteGroupDefinitionRepository>();
|
||||
services.AddTransient<ISiteGroupRepository, SiteGroupRepository>();
|
||||
|
||||
// managers
|
||||
services.AddTransient<IDBContextDependencies, DBContextDependencies>();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
778
Oqtane.Server/Infrastructure/Jobs/SynchronizationJob.cs
Normal file
778
Oqtane.Server/Infrastructure/Jobs/SynchronizationJob.cs
Normal file
@@ -0,0 +1,778 @@
|
||||
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 (ie. using "-" as a module title is problematic ie. content as configuration)
|
||||
// relies on Module.ModifiedOn to be set if the module content changes (for efficiency)
|
||||
// modules must implement ISynchronizable interface (new interface as IPortable was generally only implemented in an additive manner)
|
||||
|
||||
// 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 siteGroupDefinitionRepository = provider.GetRequiredService<ISiteGroupDefinitionRepository>();
|
||||
var siteGroupRepository = provider.GetRequiredService<ISiteGroupRepository>();
|
||||
var siteRepository = provider.GetRequiredService<ISiteRepository>();
|
||||
var aliasRepository = provider.GetRequiredService<IAliasRepository>();
|
||||
var tenantManager = provider.GetRequiredService<ITenantManager>();
|
||||
var settingRepository = provider.GetRequiredService<ISettingRepository>();
|
||||
|
||||
List<SiteGroup> siteGroups = null;
|
||||
List<Site> sites = null;
|
||||
List<Alias> aliases = null;
|
||||
|
||||
// get groups
|
||||
var groups = siteGroupDefinitionRepository.GetSiteGroupDefinitions();
|
||||
|
||||
// iterate through groups which need to be synchronized
|
||||
foreach (var group in groups.Where(item => item.Synchronize))
|
||||
{
|
||||
// get data
|
||||
if (siteGroups == null)
|
||||
{
|
||||
siteGroups = siteGroupRepository.GetSiteGroups().ToList();
|
||||
sites = siteRepository.GetSites().ToList();
|
||||
aliases = aliasRepository.GetAliases().ToList();
|
||||
}
|
||||
|
||||
var aliasName = "https://" + aliases.First(item => item.TenantId == tenantManager.GetTenant().TenantId && item.SiteId == group.PrimarySiteId && item.IsDefault).Name;
|
||||
log += $"Processing Primary Site: {sites.First(item => item.SiteId == group.PrimarySiteId).Name} - {aliasName}<br />";
|
||||
|
||||
// get primary site
|
||||
var primarySite = sites.FirstOrDefault(item => item.SiteId == group.PrimarySiteId);
|
||||
if (primarySite != null)
|
||||
{
|
||||
// update flag to prevent job from processing group again
|
||||
group.Synchronize = false;
|
||||
siteGroupDefinitionRepository.UpdateSiteGroupDefinition(group);
|
||||
|
||||
// iterate through sites in group
|
||||
foreach (var siteGroup in siteGroups.Where(item => item.SiteGroupDefinitionId == group.SiteGroupDefinitionId && item.SiteId != group.PrimarySiteId))
|
||||
{
|
||||
// get secondary site
|
||||
var secondarySite = sites.FirstOrDefault(item => item.SiteId == siteGroup.SiteId);
|
||||
if (secondarySite != null)
|
||||
{
|
||||
// get default alias for site
|
||||
siteGroup.AliasName = "https://" + aliases.First(item => item.TenantId == tenantManager.GetTenant().TenantId && item.SiteId == siteGroup.SiteId && item.IsDefault).Name;
|
||||
|
||||
// initialize SynchronizedOn
|
||||
if (siteGroup.SynchronizedOn == null)
|
||||
{
|
||||
siteGroup.SynchronizedOn = DateTime.MinValue;
|
||||
}
|
||||
|
||||
// replicate site
|
||||
var siteLog = ReplicateSite(provider, tenantManager, settingRepository, siteGroup, primarySite, secondarySite);
|
||||
|
||||
// set synchronized on date/time
|
||||
siteGroup.SynchronizedOn = DateTime.UtcNow;
|
||||
siteGroupRepository.UpdateSiteGroup(siteGroup);
|
||||
|
||||
log += $"Processed Target Site: {secondarySite.Name} - {siteGroup.AliasName}<br />" + siteLog;
|
||||
}
|
||||
else
|
||||
{
|
||||
log += $"Site Group Has A SiteId {siteGroup.SiteId} Which Does Not Exist<br />";
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
log += $"Site Group Has A PrimarySiteId {group.PrimarySiteId} Which Does Not Exist<br />";
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(log))
|
||||
{
|
||||
log = "No Site Groups Require Replication<br />";
|
||||
}
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
private string ReplicateSite(IServiceProvider provider, ITenantManager tenantManager, ISettingRepository settingRepository, SiteGroup siteGroup, Site primarySite, Site secondarySite)
|
||||
{
|
||||
var log = "";
|
||||
|
||||
// replicate roles/users
|
||||
log += ReplicateRoles(provider, settingRepository, siteGroup, primarySite.SiteId, secondarySite.SiteId);
|
||||
|
||||
// replicate folders/files
|
||||
log += ReplicateFolders(provider, settingRepository, siteGroup, primarySite.SiteId, secondarySite.SiteId);
|
||||
|
||||
// replicate pages/modules
|
||||
log += ReplicatePages(provider, settingRepository, tenantManager, siteGroup, primarySite.SiteId, secondarySite.SiteId);
|
||||
|
||||
// replicate site
|
||||
if (primarySite.ModifiedOn > siteGroup.SynchronizedOn)
|
||||
{
|
||||
secondarySite.TimeZoneId = primarySite.TimeZoneId;
|
||||
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>();
|
||||
if (siteGroup.Synchronize)
|
||||
{
|
||||
siteRepository.UpdateSite(secondarySite);
|
||||
}
|
||||
log += Log(siteGroup, $"Secondary Site Updated: {secondarySite.Name}");
|
||||
}
|
||||
|
||||
// site settings
|
||||
log += ReplicateSettings(settingRepository, siteGroup, EntityNames.Site, primarySite.SiteId, secondarySite.SiteId);
|
||||
|
||||
if (siteGroup.SynchronizedOn == DateTime.MinValue || !string.IsNullOrEmpty(log))
|
||||
{
|
||||
// clear cache for secondary site if any content was replicated
|
||||
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 (!string.IsNullOrEmpty(log) && !string.IsNullOrEmpty(siteGroup.NotifyRoleName))
|
||||
{
|
||||
// send change log to users in role
|
||||
SendNotifications(provider, secondarySite.SiteId, secondarySite.Name, siteGroup.NotifyRoleName, 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 ReplicateRoles(IServiceProvider provider, ISettingRepository settingRepository, SiteGroup siteGroup, 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)
|
||||
{
|
||||
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 > siteGroup.SynchronizedOn)
|
||||
{
|
||||
// set all properties
|
||||
secondaryRole.Name = primaryRole.Name;
|
||||
secondaryRole.Description = primaryRole.Description;
|
||||
secondaryRole.IsAutoAssigned = primaryRole.IsAutoAssigned;
|
||||
secondaryRole.IsSystem = primaryRole.IsSystem;
|
||||
|
||||
if (role == null)
|
||||
{
|
||||
if (siteGroup.Synchronize)
|
||||
{
|
||||
roleRepository.AddRole(secondaryRole);
|
||||
}
|
||||
log += Log(siteGroup, $"Role Added: {secondaryRole.Name}");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (siteGroup.Synchronize)
|
||||
{
|
||||
roleRepository.UpdateRole(secondaryRole);
|
||||
}
|
||||
log += Log(siteGroup, $"Role Updated: {secondaryRole.Name}");
|
||||
secondaryRoles.Remove(role);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove roles in the secondary site which do not exist in the primary site
|
||||
foreach (var secondaryRole in secondaryRoles.Where(item => !primaryRoles.Select(item => item.Name).Contains(item.Name)))
|
||||
{
|
||||
if (siteGroup.Synchronize)
|
||||
{
|
||||
roleRepository.DeleteRole(secondaryRole.RoleId);
|
||||
}
|
||||
log += Log(siteGroup, $"Role Deleted: {secondaryRole.Name}");
|
||||
}
|
||||
|
||||
// settings
|
||||
log += ReplicateSettings(settingRepository, siteGroup, EntityNames.Role, primarySiteId, secondarySiteId);
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
private string ReplicateFolders(IServiceProvider provider, ISettingRepository settingRepository, SiteGroup siteGroup, 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)
|
||||
{
|
||||
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 > siteGroup.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 = ReplicatePermissions(primaryFolder.PermissionList, secondarySiteId);
|
||||
|
||||
if (folder == null)
|
||||
{
|
||||
if (siteGroup.Synchronize)
|
||||
{
|
||||
folderRepository.AddFolder(secondaryFolder);
|
||||
}
|
||||
log += Log(siteGroup, $"Folder Added: {secondaryFolder.Path}");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (siteGroup.Synchronize)
|
||||
{
|
||||
folderRepository.UpdateFolder(secondaryFolder);
|
||||
}
|
||||
log += Log(siteGroup, $"Folder Updated: {secondaryFolder.Path}");
|
||||
secondaryFolders.Remove(folder);
|
||||
}
|
||||
}
|
||||
|
||||
// folder settings
|
||||
log += ReplicateSettings(settingRepository, siteGroup, EntityNames.Folder, primaryFolder.FolderId, secondaryFolder.FolderId);
|
||||
|
||||
// get files for folder
|
||||
var primaryFiles = fileRepository.GetFiles(primaryFolder.FolderId);
|
||||
var secondaryFiles = fileRepository.GetFiles(secondaryFolder.FolderId).ToList();
|
||||
|
||||
foreach (var primaryFile in primaryFiles)
|
||||
{
|
||||
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 > siteGroup.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)
|
||||
{
|
||||
if (siteGroup.Synchronize)
|
||||
{
|
||||
fileRepository.AddFile(secondaryFile);
|
||||
ReplicateFile(folderRepository, primaryFolder, primaryFile, secondaryFolder, secondaryFile);
|
||||
}
|
||||
log += Log(siteGroup, $"File Added: {siteGroup.AliasName}{secondaryFolder.Path}{secondaryFile.Name}");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (siteGroup.Synchronize)
|
||||
{
|
||||
fileRepository.UpdateFile(secondaryFile);
|
||||
ReplicateFile(folderRepository, primaryFolder, primaryFile, secondaryFolder, secondaryFile);
|
||||
}
|
||||
log += Log(siteGroup, $"File Updated: {siteGroup.AliasName}{secondaryFolder.Path}{secondaryFile.Name}");
|
||||
secondaryFiles.Remove(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove files in the secondary site which do not exist in the primary site
|
||||
foreach (var secondaryFile in secondaryFiles.Where(item => !primaryFiles.Select(item => item.Name).Contains(item.Name)))
|
||||
{
|
||||
if (siteGroup.Synchronize)
|
||||
{
|
||||
fileRepository.DeleteFile(secondaryFile.FileId);
|
||||
var secondaryPath = Path.Combine(folderRepository.GetFolderPath(secondaryFolder), secondaryFile.Name);
|
||||
System.IO.File.Delete(secondaryPath);
|
||||
}
|
||||
log += Log(siteGroup, $"File Deleted: {siteGroup.AliasName}{secondaryFolder.Path}{secondaryFile.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
// remove folders in the secondary site which do not exist in the primary site
|
||||
foreach (var secondaryFolder in secondaryFolders.Where(item => !primaryFolders.Select(item => item.Path).Contains(item.Path)))
|
||||
{
|
||||
if (siteGroup.Synchronize)
|
||||
{
|
||||
folderRepository.DeleteFolder(secondaryFolder.FolderId);
|
||||
}
|
||||
log += Log(siteGroup, $"Folder Deleted: {secondaryFolder.Path}");
|
||||
}
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
private void ReplicateFile(IFolderRepository folderRepository, Folder primaryFolder, Models.File primaryFile, Folder secondaryFolder, Models.File secondaryFile)
|
||||
{
|
||||
var primaryPath = Path.Combine(folderRepository.GetFolderPath(primaryFolder), primaryFile.Name);
|
||||
var secondaryPath = Path.Combine(folderRepository.GetFolderPath(secondaryFolder), secondaryFile.Name);
|
||||
System.IO.File.Copy(primaryPath, secondaryPath, true);
|
||||
}
|
||||
|
||||
private string ReplicatePages(IServiceProvider provider, ISettingRepository settingRepository, ITenantManager tenantManager, SiteGroup siteGroup, int primarySiteId, int secondarySiteId)
|
||||
{
|
||||
var pageRepository = provider.GetRequiredService<IPageRepository>();
|
||||
var pageModuleRepository = provider.GetRequiredService<IPageModuleRepository>();
|
||||
var moduleRepository = provider.GetRequiredService<IModuleRepository>();
|
||||
var log = "";
|
||||
|
||||
List<PageModule> primaryPageModules = null;
|
||||
List<PageModule> secondaryPageModules = null;
|
||||
|
||||
int tenantId = tenantManager.GetTenant().TenantId;
|
||||
|
||||
// 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)
|
||||
{
|
||||
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 > siteGroup.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 = ReplicatePermissions(primaryPage.PermissionList, secondarySiteId);
|
||||
|
||||
if (page == null)
|
||||
{
|
||||
if (siteGroup.Synchronize)
|
||||
{
|
||||
secondaryPage = pageRepository.AddPage(secondaryPage);
|
||||
}
|
||||
log += Log(siteGroup, $"Page Added: {siteGroup.AliasName}{secondaryPage.Path}");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (siteGroup.Synchronize)
|
||||
{
|
||||
secondaryPage = pageRepository.UpdatePage(secondaryPage);
|
||||
}
|
||||
log += Log(siteGroup, $"Page Updated: {siteGroup.AliasName}{secondaryPage.Path}");
|
||||
secondaryPages.Remove(page);
|
||||
}
|
||||
}
|
||||
|
||||
// page settings
|
||||
log += ReplicateSettings(settingRepository, siteGroup, EntityNames.Page, primaryPage.PageId, secondaryPage.PageId);
|
||||
|
||||
// modules
|
||||
if (primaryPageModules == null)
|
||||
{
|
||||
tenantManager.SetAlias(tenantId, primarySiteId); // required by ModuleDefinitionRepository.LoadModuleDefinitions()
|
||||
primaryPageModules = pageModuleRepository.GetPageModules(primarySiteId).ToList();
|
||||
}
|
||||
if (secondaryPageModules == null)
|
||||
{
|
||||
tenantManager.SetAlias(tenantId, secondarySiteId); // required by ModuleDefinitionRepository.LoadModuleDefinitions()
|
||||
secondaryPageModules = pageModuleRepository.GetPageModules(secondarySiteId).ToList();
|
||||
}
|
||||
foreach (var primaryPageModule in primaryPageModules.Where(item => item.PageId == primaryPage.PageId))
|
||||
{
|
||||
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 > siteGroup.SynchronizedOn || primaryPageModule.Module.ModifiedOn > siteGroup.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 = ReplicatePermissions(primaryPageModule.Module.PermissionList, secondarySiteId);
|
||||
secondaryPageModule.Module.AllPages = false;
|
||||
secondaryPageModule.Module.IsDeleted = false;
|
||||
|
||||
var updateContent = false;
|
||||
|
||||
if (pageModule == null)
|
||||
{
|
||||
// check if module exists
|
||||
var module = secondaryPageModules.FirstOrDefault(item => item.Module.ModuleDefinitionName == primaryPageModule.Module.ModuleDefinitionName && item.Title.ToLower() == primaryPageModule.Title.ToLower())?.Module;
|
||||
if (module == null)
|
||||
{
|
||||
// add new module
|
||||
if (siteGroup.Synchronize)
|
||||
{
|
||||
module = moduleRepository.AddModule(secondaryPageModule.Module);
|
||||
updateContent = true;
|
||||
}
|
||||
log += Log(siteGroup, $"Module Added: {module.Title} - {siteGroup.AliasName}{secondaryPage.Path}");
|
||||
}
|
||||
if (module != null)
|
||||
{
|
||||
secondaryPageModule.ModuleId = module.ModuleId;
|
||||
secondaryPageModule.Module = null; // remove tracking
|
||||
if (siteGroup.Synchronize)
|
||||
{
|
||||
secondaryPageModule = pageModuleRepository.AddPageModule(secondaryPageModule);
|
||||
}
|
||||
log += Log(siteGroup, $"Page Module Added: {module.Title} - {siteGroup.AliasName}{secondaryPage.Path}");
|
||||
secondaryPageModule.Module = module;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// update existing module
|
||||
if (primaryPageModule.Module.ModifiedOn > siteGroup.SynchronizedOn)
|
||||
{
|
||||
if (siteGroup.Synchronize)
|
||||
{
|
||||
moduleRepository.UpdateModule(secondaryPageModule.Module);
|
||||
updateContent = true;
|
||||
}
|
||||
log += Log(siteGroup, $"Module Updated: {secondaryPageModule.Title} - {siteGroup.AliasName}{secondaryPage.Path}");
|
||||
}
|
||||
if (primaryPageModule.ModifiedOn > siteGroup.SynchronizedOn)
|
||||
{
|
||||
if (siteGroup.Synchronize)
|
||||
{
|
||||
secondaryPageModule = pageModuleRepository.UpdatePageModule(secondaryPageModule);
|
||||
}
|
||||
log += Log(siteGroup, $"Page Module Updated: {secondaryPageModule.Title} - {siteGroup.AliasName}{secondaryPage.Path}");
|
||||
secondaryPageModules.Remove(pageModule);
|
||||
}
|
||||
}
|
||||
|
||||
// module content
|
||||
if (updateContent && 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 primaryModuleContent = ((ISynchronizable)moduleObject).ExtractModule(primaryPageModule.Module);
|
||||
var secondaryModuleContent = ((ISynchronizable)moduleObject).ExtractModule(secondaryPageModule.Module);
|
||||
if (primaryModuleContent != secondaryModuleContent)
|
||||
{
|
||||
if (siteGroup.Synchronize)
|
||||
{
|
||||
((ISynchronizable)moduleObject).LoadModule(secondaryPageModule.Module, primaryModuleContent, primaryPageModule.Module.ModuleDefinition.Version);
|
||||
}
|
||||
log += Log(siteGroup, $"Module Content Updated: {secondaryPageModule.Title} - {siteGroup.AliasName}{secondaryPage.Path}");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// error exporting/importing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// module settings
|
||||
log += ReplicateSettings(settingRepository, siteGroup, EntityNames.Module, primaryPageModule.ModuleId, secondaryPageModule.ModuleId);
|
||||
}
|
||||
}
|
||||
|
||||
// remove modules in the secondary site which do not exist in the primary site
|
||||
foreach (var secondaryPageModule in secondaryPageModules)
|
||||
{
|
||||
var primaryPageId = -1;
|
||||
var secondaryPage = secondaryPages.FirstOrDefault(item => item.PageId == secondaryPageModule.PageId);
|
||||
if (secondaryPage != null)
|
||||
{
|
||||
var primaryPage = primaryPages.FirstOrDefault(item => item.Path == secondaryPage.Path);
|
||||
if (primaryPage != null)
|
||||
{
|
||||
primaryPageId = primaryPage.PageId;
|
||||
}
|
||||
}
|
||||
if (!primaryPageModules.Any(item => item.PageId == primaryPageId && item.Module.ModuleDefinitionName == secondaryPageModule.Module.ModuleDefinitionName && item.Title.ToLower() == secondaryPageModule.Title.ToLower()))
|
||||
{
|
||||
if (siteGroup.Synchronize)
|
||||
{
|
||||
pageModuleRepository.DeletePageModule(secondaryPageModule.PageModuleId);
|
||||
}
|
||||
log += Log(siteGroup, $"Page Module Deleted: {secondaryPageModule.Title} - {siteGroup.AliasName}{secondaryPageModule.Page.Path}");
|
||||
}
|
||||
}
|
||||
|
||||
// remove pages in the secondary site which do not exist in the primary site
|
||||
foreach (var secondaryPage in secondaryPages.Where(item => !primaryPages.Select(item => item.Path).Contains(item.Path)))
|
||||
{
|
||||
if (siteGroup.Synchronize)
|
||||
{
|
||||
pageRepository.DeletePage(secondaryPage.PageId);
|
||||
}
|
||||
log += Log(siteGroup, $"Page Deleted: {siteGroup.AliasName}{secondaryPage.Path}");
|
||||
}
|
||||
|
||||
if (siteGroup.SynchronizedOn == DateTime.MinValue || !string.IsNullOrEmpty(log))
|
||||
{
|
||||
// clear cache for secondary site if any content was replicated
|
||||
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 List<Permission> ReplicatePermissions(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 ReplicateSettings(ISettingRepository settingRepository, SiteGroup siteGroup, 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))
|
||||
{
|
||||
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 (siteGroup.Synchronize && !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 (siteGroup.Synchronize && !excludedSettings.Any(item => item.EntityName == secondarySetting.EntityName && item.SettingName == secondarySetting.SettingName))
|
||||
{
|
||||
settingRepository.UpdateSetting(secondarySetting);
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
secondarySettings.Remove(secondarySetting);
|
||||
}
|
||||
}
|
||||
|
||||
// any remaining secondary settings need to be deleted
|
||||
foreach (var secondarySetting in secondarySettings)
|
||||
{
|
||||
if (siteGroup.Synchronize && !excludedSettings.Any(item => item.EntityName == secondarySetting.EntityName && item.SettingName == secondarySetting.SettingName))
|
||||
{
|
||||
settingRepository.DeleteSetting(secondarySetting.EntityName, secondarySetting.SettingId);
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (updated)
|
||||
{
|
||||
log += Log(siteGroup, $"{entityName} Settings Updated");
|
||||
}
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
private void SendNotifications(IServiceProvider provider, int siteId, string siteName, string roleName, string log)
|
||||
{
|
||||
var userRoleRepository = provider.GetRequiredService<IUserRoleRepository>();
|
||||
var notificationRepository = provider.GetRequiredService<INotificationRepository>();
|
||||
|
||||
foreach (var userRole in userRoleRepository.GetUserRoles(roleName, siteId))
|
||||
{
|
||||
var notification = new Notification(siteId, userRole.User, $"{siteName} Change Log", log);
|
||||
notificationRepository.AddNotification(notification);
|
||||
}
|
||||
}
|
||||
|
||||
private string Log(SiteGroup siteGroup, string content)
|
||||
{
|
||||
// not necessary to log initial replication
|
||||
if (siteGroup.SynchronizedOn != DateTime.MinValue)
|
||||
{
|
||||
return content + "<br />";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,9 @@ namespace Oqtane.Infrastructure
|
||||
|
||||
public string[] GetSupportedCultures()
|
||||
{
|
||||
return CultureInfo.GetCultures(CultureTypes.AllCultures).Select(item => item.Name).OrderBy(c => c).ToArray();
|
||||
return CultureInfo.GetCultures(CultureTypes.AllCultures)
|
||||
.Where(item => item.Name.Length == 2) // major languages only (this could be configurable)
|
||||
.Select(item => item.Name).OrderBy(c => c).ToArray();
|
||||
}
|
||||
|
||||
public string[] GetInstalledCultures()
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
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 SiteGroupDefinitionEntityBuilder : AuditableBaseEntityBuilder<SiteGroupDefinitionEntityBuilder>
|
||||
{
|
||||
private const string _entityTableName = "SiteGroupDefinition";
|
||||
private readonly PrimaryKey<SiteGroupDefinitionEntityBuilder> _primaryKey = new("PK_SiteGroupDefinition", x => x.SiteGroupDefinitionId);
|
||||
|
||||
public SiteGroupDefinitionEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
|
||||
{
|
||||
EntityTableName = _entityTableName;
|
||||
PrimaryKey = _primaryKey;
|
||||
}
|
||||
|
||||
protected override SiteGroupDefinitionEntityBuilder BuildTable(ColumnsBuilder table)
|
||||
{
|
||||
SiteGroupDefinitionId = AddAutoIncrementColumn(table, "SiteGroupDefinitionId");
|
||||
Name = AddStringColumn(table, "Name", 200);
|
||||
PrimarySiteId = AddIntegerColumn(table, "PrimarySiteId");
|
||||
Synchronization = AddBooleanColumn(table, "Synchronization");
|
||||
Synchronize = AddBooleanColumn(table, "Synchronize");
|
||||
Localization = AddBooleanColumn(table, "Localization");
|
||||
|
||||
AddAuditableColumns(table);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public OperationBuilder<AddColumnOperation> SiteGroupDefinitionId { get; set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> Name { get; set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> PrimarySiteId { get; set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> Synchronization { get; set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> Synchronize { get; set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> Localization { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
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);
|
||||
private readonly ForeignKey<SiteGroupEntityBuilder> _groupForeignKey = new("FK_SiteGroup_SiteGroupDefinition", x => x.SiteGroupDefinitionId, "SiteGroupDefinition", "SiteGroupDefinitionId", ReferentialAction.Cascade);
|
||||
private readonly ForeignKey<SiteGroupEntityBuilder> _siteForeignKey = new("FK_SiteGroup_Site", x => x.SiteId, "Site", "SiteId", ReferentialAction.Cascade);
|
||||
|
||||
public SiteGroupEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
|
||||
{
|
||||
EntityTableName = _entityTableName;
|
||||
PrimaryKey = _primaryKey;
|
||||
ForeignKeys.Add(_groupForeignKey);
|
||||
ForeignKeys.Add(_siteForeignKey);
|
||||
}
|
||||
|
||||
protected override SiteGroupEntityBuilder BuildTable(ColumnsBuilder table)
|
||||
{
|
||||
SiteGroupId = AddAutoIncrementColumn(table, "SiteGroupId");
|
||||
SiteGroupDefinitionId = AddIntegerColumn(table, "SiteGroupDefinitionId");
|
||||
SiteId = AddIntegerColumn(table, "SiteId");
|
||||
Synchronize = AddBooleanColumn(table, "Synchronize");
|
||||
NotifyRoleName = AddStringColumn(table, "NotifyRoleName", 256, true);
|
||||
SynchronizedOn = AddDateTimeColumn(table, "SynchronizedOn", true);
|
||||
|
||||
AddAuditableColumns(table);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public OperationBuilder<AddColumnOperation> SiteGroupId { get; set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> SiteGroupDefinitionId { get; set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> SiteId { get; set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> Synchronize { get; set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> NotifyRoleName { get; set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> SynchronizedOn { get; set; }
|
||||
}
|
||||
}
|
||||
31
Oqtane.Server/Migrations/Tenant/10010001_AddSiteGroups.cs
Normal file
31
Oqtane.Server/Migrations/Tenant/10010001_AddSiteGroups.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
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 siteGroupDefinitionEntityBuilder = new SiteGroupDefinitionEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
siteGroupDefinitionEntityBuilder.Create();
|
||||
|
||||
var siteGroupEntityBuilder = new SiteGroupEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
siteGroupEntityBuilder.Create();
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// not implemented
|
||||
}
|
||||
}
|
||||
}
|
||||
31
Oqtane.Server/Migrations/Tenant/10010002_AddCultureCode.cs
Normal file
31
Oqtane.Server/Migrations/Tenant/10010002_AddCultureCode.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
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);
|
||||
|
||||
var userEntityBuilder = new UserEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
userEntityBuilder.AddStringColumn("CultureCode", 10, true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// not implemented
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Oqtane.Server/Migrations/Tenant/10010003_RemoveHomePageId.cs
Normal file
28
Oqtane.Server/Migrations/Tenant/10010003_RemoveHomePageId.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ using Microsoft.Extensions.Caching.Memory;
|
||||
namespace Oqtane.Modules.HtmlText.Manager
|
||||
{
|
||||
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
|
||||
public class HtmlTextManager : MigratableModuleBase, IInstallable, IPortable, ISearchable
|
||||
public class HtmlTextManager : MigratableModuleBase, IInstallable, IPortable, ISynchronizable, ISearchable
|
||||
{
|
||||
private readonly IHtmlTextRepository _htmlText;
|
||||
private readonly IDBContextDependencies _DBContextDependencies;
|
||||
@@ -41,7 +41,45 @@ namespace Oqtane.Modules.HtmlText.Manager
|
||||
_cache = cache;
|
||||
}
|
||||
|
||||
// IInstallable implementation
|
||||
public bool Install(Tenant tenant, string version)
|
||||
{
|
||||
if (tenant.DBType == Constants.DefaultDBType && version == "1.0.1")
|
||||
{
|
||||
// version 1.0.0 used SQL scripts rather than migrations, so we need to seed the migration history table
|
||||
_sqlRepository.ExecuteNonQuery(tenant, MigrationUtils.BuildInsertScript("HtmlText.01.00.00.00"));
|
||||
}
|
||||
return Migrate(new HtmlTextContext(_DBContextDependencies), tenant, MigrationType.Up);
|
||||
}
|
||||
|
||||
public bool Uninstall(Tenant tenant)
|
||||
{
|
||||
return Migrate(new HtmlTextContext(_DBContextDependencies), tenant, MigrationType.Down);
|
||||
}
|
||||
|
||||
// IPortable implementation
|
||||
public string ExportModule(Module module)
|
||||
{
|
||||
return GetModuleContent(module);
|
||||
}
|
||||
|
||||
public void ImportModule(Module module, string content, string version)
|
||||
{
|
||||
SaveModuleContent(module, content, version);
|
||||
}
|
||||
|
||||
// ISynchronizable implementation
|
||||
public string ExtractModule(Module module)
|
||||
{
|
||||
return GetModuleContent(module);
|
||||
}
|
||||
|
||||
public void LoadModule(Module module, string content, string version)
|
||||
{
|
||||
SaveModuleContent(module, content, version);
|
||||
}
|
||||
|
||||
private string GetModuleContent(Module module)
|
||||
{
|
||||
string content = "";
|
||||
var htmltexts = _htmlText.GetHtmlTexts(module.ModuleId);
|
||||
@@ -53,6 +91,23 @@ namespace Oqtane.Modules.HtmlText.Manager
|
||||
return content;
|
||||
}
|
||||
|
||||
private void SaveModuleContent(Module module, string content, string version)
|
||||
{
|
||||
content = WebUtility.HtmlDecode(content);
|
||||
var htmlText = new Models.HtmlText();
|
||||
htmlText.ModuleId = module.ModuleId;
|
||||
htmlText.Content = content;
|
||||
_htmlText.AddHtmlText(htmlText);
|
||||
|
||||
//clear the cache for the module
|
||||
var alias = _tenantManager.GetAlias();
|
||||
if (alias != null)
|
||||
{
|
||||
_cache.Remove($"HtmlText:{alias.SiteKey}:{module.ModuleId}");
|
||||
}
|
||||
}
|
||||
|
||||
// ISearchable implementation
|
||||
public Task<List<SearchContent>> GetSearchContentsAsync(PageModule pageModule, DateTime lastIndexedOn)
|
||||
{
|
||||
var searchContents = new List<SearchContent>();
|
||||
@@ -70,36 +125,5 @@ namespace Oqtane.Modules.HtmlText.Manager
|
||||
|
||||
return Task.FromResult(searchContents);
|
||||
}
|
||||
|
||||
public void ImportModule(Module module, string content, string version)
|
||||
{
|
||||
content = WebUtility.HtmlDecode(content);
|
||||
var htmlText = new Models.HtmlText();
|
||||
htmlText.ModuleId = module.ModuleId;
|
||||
htmlText.Content = content;
|
||||
_htmlText.AddHtmlText(htmlText);
|
||||
|
||||
//clear the cache for the module
|
||||
var alias = _tenantManager.GetAlias();
|
||||
if(alias != null)
|
||||
{
|
||||
_cache.Remove($"HtmlText:{alias.SiteKey}:{module.ModuleId}");
|
||||
}
|
||||
}
|
||||
|
||||
public bool Install(Tenant tenant, string version)
|
||||
{
|
||||
if (tenant.DBType == Constants.DefaultDBType && version == "1.0.1")
|
||||
{
|
||||
// version 1.0.0 used SQL scripts rather than migrations, so we need to seed the migration history table
|
||||
_sqlRepository.ExecuteNonQuery(tenant, MigrationUtils.BuildInsertScript("HtmlText.01.00.00.00"));
|
||||
}
|
||||
return Migrate(new HtmlTextContext(_DBContextDependencies), tenant, MigrationType.Up);
|
||||
}
|
||||
|
||||
public bool Uninstall(Tenant tenant)
|
||||
{
|
||||
return Migrate(new HtmlTextContext(_DBContextDependencies), tenant, MigrationType.Down);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,29 @@ using System.Linq;
|
||||
using Oqtane.Documentation;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Oqtane.Repository;
|
||||
|
||||
namespace Oqtane.Modules.HtmlText.Repository
|
||||
{
|
||||
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
|
||||
public interface IHtmlTextRepository
|
||||
{
|
||||
IEnumerable<Models.HtmlText> GetHtmlTexts(int moduleId);
|
||||
Models.HtmlText GetHtmlText(int htmlTextId);
|
||||
Models.HtmlText AddHtmlText(Models.HtmlText htmlText);
|
||||
void DeleteHtmlText(int htmlTextId);
|
||||
}
|
||||
|
||||
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
|
||||
public class HtmlTextRepository : IHtmlTextRepository, ITransientService
|
||||
{
|
||||
private readonly IDbContextFactory<HtmlTextContext> _factory;
|
||||
private readonly IModuleRepository _moduleRepository;
|
||||
|
||||
public HtmlTextRepository(IDbContextFactory<HtmlTextContext> factory)
|
||||
public HtmlTextRepository(IDbContextFactory<HtmlTextContext> factory, IModuleRepository moduleRepository)
|
||||
{
|
||||
_factory = factory;
|
||||
_moduleRepository = moduleRepository;
|
||||
}
|
||||
|
||||
public IEnumerable<Models.HtmlText> GetHtmlTexts(int moduleId)
|
||||
@@ -32,6 +44,11 @@ namespace Oqtane.Modules.HtmlText.Repository
|
||||
using var db = _factory.CreateDbContext();
|
||||
db.HtmlText.Add(htmlText);
|
||||
db.SaveChanges();
|
||||
|
||||
// update module ModifiedOn date
|
||||
var module = _moduleRepository.GetModule(htmlText.ModuleId);
|
||||
_moduleRepository.UpdateModule(module);
|
||||
|
||||
return htmlText;
|
||||
}
|
||||
|
||||
@@ -39,8 +56,15 @@ namespace Oqtane.Modules.HtmlText.Repository
|
||||
{
|
||||
using var db = _factory.CreateDbContext();
|
||||
Models.HtmlText htmlText = db.HtmlText.FirstOrDefault(item => item.HtmlTextId == htmlTextId);
|
||||
if (htmlText != null) db.HtmlText.Remove(htmlText);
|
||||
db.SaveChanges();
|
||||
if (htmlText != null)
|
||||
{
|
||||
db.HtmlText.Remove(htmlText);
|
||||
db.SaveChanges();
|
||||
|
||||
// update module ModifiedOn date
|
||||
var module = _moduleRepository.GetModule(htmlText.ModuleId);
|
||||
_moduleRepository.UpdateModule(module);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Oqtane.Documentation;
|
||||
|
||||
namespace Oqtane.Modules.HtmlText.Repository
|
||||
{
|
||||
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
|
||||
public interface IHtmlTextRepository
|
||||
{
|
||||
IEnumerable<Models.HtmlText> GetHtmlTexts(int moduleId);
|
||||
Models.HtmlText GetHtmlText(int htmlTextId);
|
||||
Models.HtmlText AddHtmlText(Models.HtmlText htmlText);
|
||||
void DeleteHtmlText(int htmlTextId);
|
||||
}
|
||||
}
|
||||
13
Oqtane.Server/Modules/ISynchronizable.cs
Normal file
13
Oqtane.Server/Modules/ISynchronizable.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Oqtane.Models;
|
||||
|
||||
namespace Oqtane.Modules
|
||||
{
|
||||
public interface ISynchronizable
|
||||
{
|
||||
// You Must Set The "ServerManagerType" In Your IModule Interface
|
||||
|
||||
string ExtractModule(Module module);
|
||||
|
||||
void LoadModule(Module module, string content, string version);
|
||||
}
|
||||
}
|
||||
@@ -134,5 +134,7 @@ namespace Oqtane.Repository
|
||||
public virtual DbSet<SearchContentWord> SearchContentWord { get; set; }
|
||||
public virtual DbSet<SearchWord> SearchWord { get; set; }
|
||||
public virtual DbSet<MigrationHistory> MigrationHistory { get; set; }
|
||||
public virtual DbSet<SiteGroupDefinition> SiteGroupDefinition { get; set; }
|
||||
public virtual DbSet<SiteGroup> SiteGroup { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
75
Oqtane.Server/Repository/SiteGroupDefinitionRepository.cs
Normal file
75
Oqtane.Server/Repository/SiteGroupDefinitionRepository.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Oqtane.Models;
|
||||
|
||||
namespace Oqtane.Repository
|
||||
{
|
||||
public interface ISiteGroupDefinitionRepository
|
||||
{
|
||||
IEnumerable<SiteGroupDefinition> GetSiteGroupDefinitions();
|
||||
SiteGroupDefinition AddSiteGroupDefinition(SiteGroupDefinition siteGroupDefinition);
|
||||
SiteGroupDefinition UpdateSiteGroupDefinition(SiteGroupDefinition siteGroupDefinition);
|
||||
SiteGroupDefinition GetSiteGroupDefinition(int siteGroupDefinitionId);
|
||||
SiteGroupDefinition GetSiteGroupDefinition(int siteGroupDefinitionId, bool tracking);
|
||||
void DeleteSiteGroupDefinition(int siteGroupDefinitionId);
|
||||
}
|
||||
|
||||
public class SiteGroupDefinitionRepository : ISiteGroupDefinitionRepository
|
||||
{
|
||||
private readonly IDbContextFactory<TenantDBContext> _dbContextFactory;
|
||||
|
||||
public SiteGroupDefinitionRepository(IDbContextFactory<TenantDBContext> dbContextFactory)
|
||||
{
|
||||
_dbContextFactory = dbContextFactory;
|
||||
}
|
||||
|
||||
public IEnumerable<SiteGroupDefinition> GetSiteGroupDefinitions()
|
||||
{
|
||||
using var db = _dbContextFactory.CreateDbContext();
|
||||
return db.SiteGroupDefinition.ToList();
|
||||
}
|
||||
|
||||
public SiteGroupDefinition AddSiteGroupDefinition(SiteGroupDefinition siteGroupDefinition)
|
||||
{
|
||||
using var db = _dbContextFactory.CreateDbContext();
|
||||
db.SiteGroupDefinition.Add(siteGroupDefinition);
|
||||
db.SaveChanges();
|
||||
return siteGroupDefinition;
|
||||
}
|
||||
|
||||
public SiteGroupDefinition UpdateSiteGroupDefinition(SiteGroupDefinition siteGroupDefinition)
|
||||
{
|
||||
using var db = _dbContextFactory.CreateDbContext();
|
||||
db.Entry(siteGroupDefinition).State = EntityState.Modified;
|
||||
db.SaveChanges();
|
||||
return siteGroupDefinition;
|
||||
}
|
||||
|
||||
public SiteGroupDefinition GetSiteGroupDefinition(int siteGroupDefinitionId)
|
||||
{
|
||||
return GetSiteGroupDefinition(siteGroupDefinitionId, true);
|
||||
}
|
||||
|
||||
public SiteGroupDefinition GetSiteGroupDefinition(int siteGroupDefinitionId, bool tracking)
|
||||
{
|
||||
using var db = _dbContextFactory.CreateDbContext();
|
||||
if (tracking)
|
||||
{
|
||||
return db.SiteGroupDefinition.FirstOrDefault(item => item.SiteGroupDefinitionId == siteGroupDefinitionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
return db.SiteGroupDefinition.AsNoTracking().FirstOrDefault(item => item.SiteGroupDefinitionId == siteGroupDefinitionId);
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteSiteGroupDefinition(int siteGroupDefinitionId)
|
||||
{
|
||||
using var db = _dbContextFactory.CreateDbContext();
|
||||
SiteGroupDefinition group = db.SiteGroupDefinition.Find(siteGroupDefinitionId);
|
||||
db.SiteGroupDefinition.Remove(group);
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
88
Oqtane.Server/Repository/SiteGroupRepository.cs
Normal file
88
Oqtane.Server/Repository/SiteGroupRepository.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Oqtane.Models;
|
||||
|
||||
namespace Oqtane.Repository
|
||||
{
|
||||
public interface ISiteGroupRepository
|
||||
{
|
||||
IEnumerable<SiteGroup> GetSiteGroups();
|
||||
IEnumerable<SiteGroup> GetSiteGroups(int siteId, int siteGroupDefinitionId);
|
||||
SiteGroup AddSiteGroup(SiteGroup siteGroup);
|
||||
SiteGroup UpdateSiteGroup(SiteGroup siteGroup);
|
||||
SiteGroup GetSiteGroup(int siteSiteGroupDefinitionId);
|
||||
SiteGroup GetSiteGroup(int siteSiteGroupDefinitionId, bool tracking);
|
||||
void DeleteSiteGroup(int siteSiteGroupDefinitionId);
|
||||
}
|
||||
|
||||
public class SiteGroupRepository : ISiteGroupRepository
|
||||
{
|
||||
private readonly IDbContextFactory<TenantDBContext> _dbContextFactory;
|
||||
|
||||
public SiteGroupRepository(IDbContextFactory<TenantDBContext> dbContextFactory)
|
||||
{
|
||||
_dbContextFactory = dbContextFactory;
|
||||
}
|
||||
|
||||
public IEnumerable<SiteGroup> GetSiteGroups()
|
||||
{
|
||||
return GetSiteGroups(-1, -1);
|
||||
}
|
||||
|
||||
public IEnumerable<SiteGroup> GetSiteGroups(int siteId, int siteGroupDefinitionId)
|
||||
{
|
||||
using var db = _dbContextFactory.CreateDbContext();
|
||||
return db.SiteGroup
|
||||
.Where(item => (siteId == -1 || item.SiteId == siteId) && (siteGroupDefinitionId == -1 || item.SiteGroupDefinitionId == siteGroupDefinitionId))
|
||||
.Include(item => item.SiteGroupDefinition) // eager load
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public SiteGroup AddSiteGroup(SiteGroup SiteGroup)
|
||||
{
|
||||
using var db = _dbContextFactory.CreateDbContext();
|
||||
db.SiteGroup.Add(SiteGroup);
|
||||
db.SaveChanges();
|
||||
return SiteGroup;
|
||||
}
|
||||
|
||||
public SiteGroup UpdateSiteGroup(SiteGroup SiteGroup)
|
||||
{
|
||||
using var db = _dbContextFactory.CreateDbContext();
|
||||
db.Entry(SiteGroup).State = EntityState.Modified;
|
||||
db.SaveChanges();
|
||||
return SiteGroup;
|
||||
}
|
||||
|
||||
public SiteGroup GetSiteGroup(int SiteGroupDefinitionId)
|
||||
{
|
||||
return GetSiteGroup(SiteGroupDefinitionId, true);
|
||||
}
|
||||
|
||||
public SiteGroup GetSiteGroup(int SiteGroupDefinitionId, bool tracking)
|
||||
{
|
||||
using var db = _dbContextFactory.CreateDbContext();
|
||||
if (tracking)
|
||||
{
|
||||
return db.SiteGroup
|
||||
.Include(item => item.SiteGroupDefinition) // eager load
|
||||
.FirstOrDefault(item => item.SiteGroupDefinitionId == SiteGroupDefinitionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
return db.SiteGroup.AsNoTracking()
|
||||
.Include(item => item.SiteGroupDefinition) // eager load
|
||||
.FirstOrDefault(item => item.SiteGroupDefinitionId == SiteGroupDefinitionId);
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteSiteGroup(int SiteGroupDefinitionId)
|
||||
{
|
||||
using var db = _dbContextFactory.CreateDbContext();
|
||||
SiteGroup SiteGroup = db.SiteGroup.Find(SiteGroupDefinitionId);
|
||||
db.SiteGroup.Remove(SiteGroup);
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,8 @@ namespace Oqtane.Services
|
||||
public class ServerSiteService : ISiteService
|
||||
{
|
||||
private readonly ISiteRepository _sites;
|
||||
private readonly ISiteGroupRepository _siteGroups;
|
||||
private readonly IAliasRepository _aliases;
|
||||
private readonly IPageRepository _pages;
|
||||
private readonly IThemeRepository _themes;
|
||||
private readonly IPageModuleRepository _pageModules;
|
||||
@@ -37,9 +39,11 @@ namespace Oqtane.Services
|
||||
private readonly IHttpContextAccessor _accessor;
|
||||
private readonly string _private = "[PRIVATE]";
|
||||
|
||||
public ServerSiteService(ISiteRepository sites, IPageRepository pages, IThemeRepository themes, IPageModuleRepository pageModules, IModuleDefinitionRepository moduleDefinitions, ILanguageRepository languages, IUserManager userManager, IUserPermissions userPermissions, ISettingRepository settings, ITenantManager tenantManager, ISyncManager syncManager, IConfigManager configManager, ILogManager logger, IMemoryCache cache, IHttpContextAccessor accessor)
|
||||
public ServerSiteService(ISiteRepository sites, ISiteGroupRepository siteGroups, IAliasRepository aliases, IPageRepository pages, IThemeRepository themes, IPageModuleRepository pageModules, IModuleDefinitionRepository moduleDefinitions, ILanguageRepository languages, IUserManager userManager, IUserPermissions userPermissions, ISettingRepository settings, ITenantManager tenantManager, ISyncManager syncManager, IConfigManager configManager, ILogManager logger, IMemoryCache cache, IHttpContextAccessor accessor)
|
||||
{
|
||||
_sites = sites;
|
||||
_siteGroups = siteGroups;
|
||||
_aliases = aliases;
|
||||
_pages = pages;
|
||||
_themes = themes;
|
||||
_pageModules = pageModules;
|
||||
@@ -145,12 +149,7 @@ namespace Oqtane.Services
|
||||
site.Settings.Add(Constants.PageManagementModule, modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.PageManagementModule).ModuleId.ToString());
|
||||
|
||||
// languages
|
||||
site.Languages = _languages.GetLanguages(site.SiteId).ToList();
|
||||
var defaultCulture = CultureInfo.GetCultureInfo(Constants.DefaultCulture);
|
||||
if (!site.Languages.Exists(item => item.Code == defaultCulture.Name))
|
||||
{
|
||||
site.Languages.Add(new Language { Code = defaultCulture.Name, Name = "", Version = Constants.Version, IsDefault = !site.Languages.Any(l => l.IsDefault) });
|
||||
}
|
||||
site.Languages = GetLanguages(site.SiteId, alias.TenantId);
|
||||
|
||||
// themes
|
||||
site.Themes = _themes.FilterThemes(_themes.GetThemes(site.SiteId).ToList());
|
||||
@@ -158,6 +157,7 @@ namespace Oqtane.Services
|
||||
// installation date used for fingerprinting static assets
|
||||
site.Fingerprint = Utilities.GenerateSimpleHash(_configManager.GetSetting("InstallationDate", DateTime.UtcNow.ToString("yyyyMMddHHmm")));
|
||||
|
||||
// set tenant
|
||||
site.TenantId = alias.TenantId;
|
||||
}
|
||||
else
|
||||
@@ -311,6 +311,39 @@ namespace Oqtane.Services
|
||||
return modules.OrderBy(item => item.PageId).ThenBy(item => item.Pane).ThenBy(item => item.Order).ToList();
|
||||
}
|
||||
|
||||
private List<Language> GetLanguages(int siteId, int tenantId)
|
||||
{
|
||||
var languages = new List<Language>();
|
||||
|
||||
var siteGroups = _siteGroups.GetSiteGroups();
|
||||
if (siteGroups.Any(item => item.SiteId == siteId && item.SiteGroupDefinition.Localization))
|
||||
{
|
||||
var sites = _sites.GetSites().ToList();
|
||||
var aliases = _aliases.GetAliases().ToList();
|
||||
|
||||
foreach (var siteGroupDefinitionId in siteGroups.Where(item => item.SiteId == siteId && item.SiteGroupDefinition.Localization).Select(item => item.SiteGroupDefinitionId).Distinct().ToList())
|
||||
{
|
||||
foreach (var siteGroup in siteGroups.Where(item => item.SiteGroupDefinitionId == siteGroupDefinitionId))
|
||||
{
|
||||
var site = sites.FirstOrDefault(item => item.SiteId == siteGroup.SiteId);
|
||||
if (site != null && !string.IsNullOrEmpty(site.CultureCode))
|
||||
{
|
||||
if (!languages.Any(item => item.Code == site.CultureCode))
|
||||
{
|
||||
var alias = aliases.FirstOrDefault(item => item.SiteId == siteGroup.SiteId && item.TenantId == tenantId && item.IsDefault);
|
||||
if (alias != null)
|
||||
{
|
||||
languages.Add(new Language { Code = site.CultureCode, Name = "", AliasName = alias.Name, IsDefault = true });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return languages;
|
||||
}
|
||||
|
||||
[Obsolete("This method is deprecated.", false)]
|
||||
public void SetAlias(Alias alias)
|
||||
{
|
||||
|
||||
@@ -41,6 +41,12 @@ namespace Oqtane.Models
|
||||
/// </summary>
|
||||
public string Version { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
/// <summary>
|
||||
/// The primary alias name for the site with this language
|
||||
/// </summary>
|
||||
public string AliasName { get; set; }
|
||||
|
||||
public Language Clone()
|
||||
{
|
||||
return new Language
|
||||
@@ -50,7 +56,8 @@ namespace Oqtane.Models
|
||||
Name = Name,
|
||||
Code = Code,
|
||||
IsDefault = IsDefault,
|
||||
Version = Version
|
||||
Version = Version,
|
||||
AliasName = AliasName
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,11 @@ namespace Oqtane.Models
|
||||
/// </summary>
|
||||
public string TimeZoneId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The default culture for the site (ie. en-US)
|
||||
/// </summary>
|
||||
public string CultureCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reference to a <see cref="File"/> which has the Logo for this site.
|
||||
/// Should be an image.
|
||||
@@ -120,11 +125,6 @@ namespace Oqtane.Models
|
||||
/// </summary>
|
||||
public string Version { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The home page of the site - the "/" path will be used by default if no home page is specified
|
||||
/// </summary>
|
||||
public int? HomePageId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content to be included in the head of the page
|
||||
/// </summary>
|
||||
@@ -217,6 +217,7 @@ namespace Oqtane.Models
|
||||
SiteId = SiteId,
|
||||
Name = Name,
|
||||
TimeZoneId = TimeZoneId,
|
||||
CultureCode = CultureCode,
|
||||
LogoFileId = LogoFileId,
|
||||
FaviconFileId = FaviconFileId,
|
||||
DefaultThemeType = DefaultThemeType,
|
||||
@@ -235,7 +236,6 @@ namespace Oqtane.Models
|
||||
Hybrid = Hybrid,
|
||||
EnhancedNavigation = EnhancedNavigation,
|
||||
Version = Version,
|
||||
HomePageId = HomePageId,
|
||||
HeadContent = HeadContent,
|
||||
BodyContent = BodyContent,
|
||||
IsDeleted = IsDeleted,
|
||||
@@ -262,6 +262,10 @@ namespace Oqtane.Models
|
||||
[NotMapped]
|
||||
[Obsolete("This property is deprecated.", false)]
|
||||
public string DefaultLayoutType { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
[Obsolete("This property is deprecated.", false)]
|
||||
public int? HomePageId { get; set; }
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
49
Oqtane.Shared/Models/SiteGroup.cs
Normal file
49
Oqtane.Shared/Models/SiteGroup.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Oqtane.Models
|
||||
{
|
||||
public class SiteGroup : ModelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// ID to identify the site group
|
||||
/// </summary>
|
||||
public int SiteGroupId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reference to the <see cref="SiteGroupDefinition"/>.
|
||||
/// </summary>
|
||||
public int SiteGroupDefinitionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reference to the <see cref="Site"/>.
|
||||
/// </summary>
|
||||
public int SiteId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if content should be synchronized for the site (false = compare, true = update)
|
||||
/// </summary>
|
||||
public bool Synchronize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The role who should be notified of changes to the site (non-primary site only)
|
||||
/// </summary>
|
||||
public string NotifyRoleName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The last date/time the site was synchronized
|
||||
/// </summary>
|
||||
public DateTime? SynchronizedOn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SiteGroupDefinition"/> itself.
|
||||
/// </summary>
|
||||
public SiteGroupDefinition SiteGroupDefinition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The primary alias for the site
|
||||
/// </summary>
|
||||
[NotMapped]
|
||||
public string AliasName { get; set; }
|
||||
}
|
||||
}
|
||||
35
Oqtane.Shared/Models/SiteGroupDefinition.cs
Normal file
35
Oqtane.Shared/Models/SiteGroupDefinition.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
namespace Oqtane.Models
|
||||
{
|
||||
public class SiteGroupDefinition : ModelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// ID to identify the group
|
||||
/// </summary>
|
||||
public int SiteGroupDefinitionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Name of the group
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// SiteId of the primary site in the group
|
||||
/// </summary>
|
||||
public int PrimarySiteId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the group supports synchronization
|
||||
/// </summary>
|
||||
public bool Synchronization { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies if the group needs to be synchronized
|
||||
/// </summary>
|
||||
public bool Synchronize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the group supports localization
|
||||
/// </summary>
|
||||
public bool Localization { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,11 @@ namespace Oqtane.Models
|
||||
/// </summary>
|
||||
public string TimeZoneId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The default culture for the user (ie. en-US)
|
||||
/// </summary>
|
||||
public string CultureCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Reference to a <see cref="File"/> containing the users photo.
|
||||
/// </summary>
|
||||
@@ -140,6 +145,7 @@ namespace Oqtane.Models
|
||||
DisplayName = DisplayName,
|
||||
Email = Email,
|
||||
TimeZoneId = TimeZoneId,
|
||||
CultureCode = CultureCode,
|
||||
PhotoFileId = PhotoFileId,
|
||||
LastLoginOn = LastLoginOn,
|
||||
LastIPAddress = LastIPAddress,
|
||||
|
||||
@@ -4,8 +4,8 @@ namespace Oqtane.Shared
|
||||
{
|
||||
public class Constants
|
||||
{
|
||||
public static readonly string Version = "10.0.4";
|
||||
public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,5.0.0,5.0.1,5.0.2,5.0.3,5.1.0,5.1.1,5.1.2,5.2.0,5.2.1,5.2.2,5.2.3,5.2.4,6.0.0,6.0.1,6.1.0,6.1.1,6.1.2,6.1.3,6.1.4,6.1.5,6.2.0,6.2.1,10.0.0,10.0.1,10.0.2,10.0.3,10.0.4";
|
||||
public static readonly string Version = "10.1.0";
|
||||
public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,5.0.0,5.0.1,5.0.2,5.0.3,5.1.0,5.1.1,5.1.2,5.2.0,5.2.1,5.2.2,5.2.3,5.2.4,6.0.0,6.0.1,6.1.0,6.1.1,6.1.2,6.1.3,6.1.4,6.1.5,6.2.0,6.2.1,10.0.0,10.0.1,10.0.2,10.0.3,10.0.4,10.1.0";
|
||||
public const string PackageId = "Oqtane.Framework";
|
||||
public const string ClientId = "Oqtane.Client";
|
||||
public const string UpdaterPackageId = "Oqtane.Updater";
|
||||
@@ -36,6 +36,7 @@ namespace Oqtane.Shared
|
||||
|
||||
public const string AdminSiteTemplate = "Oqtane.Infrastructure.SiteTemplates.AdminSiteTemplate, Oqtane.Server";
|
||||
public const string DefaultSiteTemplate = "Oqtane.Infrastructure.SiteTemplates.DefaultSiteTemplate, Oqtane.Server";
|
||||
public const string EmptySiteTemplate = "Oqtane.Infrastructure.SiteTemplates.EmptySiteTemplate, Oqtane.Server";
|
||||
|
||||
public static readonly string[] DefaultHostModuleTypes = new[] { "Upgrade", "Themes", "SystemInfo", "Sql", "Sites", "ModuleDefinitions", "Logs", "Jobs", "ModuleCreator" };
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ namespace Oqtane.Shared
|
||||
public const string Role = "Role";
|
||||
public const string Setting = "Setting";
|
||||
public const string Site = "Site";
|
||||
public const string SiteGroup = "SiteGroup";
|
||||
public const string SiteGroupDefinition = "SiteGroupDefinition";
|
||||
public const string Tenant = "Tenant";
|
||||
public const string Theme = "Theme";
|
||||
public const string UrlMapping = "UrlMapping";
|
||||
|
||||
Reference in New Issue
Block a user