Merge pull request #79 from oqtane/dev

sync
This commit is contained in:
Shaun Walker
2021-01-18 10:21:04 -05:00
committed by GitHub
20 changed files with 463 additions and 18 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -502,6 +502,37 @@ namespace Oqtane.Repository
}
});
pageTemplates.Add(new PageTemplate
{
Name = "Language Management",
Parent = "Admin",
Path = "admin/languages",
Icon = Icons.Text,
IsNavigation = false,
IsPersonalizable = false,
PagePermissions = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true),
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(),
PageTemplateModules = new List<PageTemplateModule>
{
new PageTemplateModule
{
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Languages.Index).ToModuleDefinitionName(), Title = "Language Management", Pane = "Content",
ModulePermissions = new List<Permission>
{
new Permission(PermissionNames.View, RoleNames.Host, true),
new Permission(PermissionNames.Edit, RoleNames.Host, true),
new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(),
Content = ""
}
}
});
pageTemplates.Add(new PageTemplate
{
Name = "Scheduled Jobs", Parent = "Admin", Path = "admin/jobs", Icon = Icons.Timer, IsNavigation = false, IsPersonalizable = false,
PagePermissions = new List<Permission>

View File

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

View File

@ -128,6 +128,7 @@ namespace Oqtane
services.AddScoped<ISqlService, SqlService>();
services.AddScoped<ISystemService, SystemService>();
services.AddScoped<ILocalizationService, LocalizationService>();
services.AddScoped<ILanguageService, LanguageService>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
@ -213,6 +214,7 @@ namespace Oqtane
services.AddTransient<ISiteTemplateRepository, SiteTemplateRepository>();
services.AddTransient<ISqlRepository, SqlRepository>();
services.AddTransient<IUpgradeManager, UpgradeManager>();
services.AddTransient<ILanguageRepository, LanguageRepository>();
// load the external assemblies into the app domain, install services
services.AddOqtane(_runtime, _supportedCultures);

View File

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