diff --git a/.github/azure-pipeline-database-ci.yml b/.github/azure-pipeline-database-ci.yml index 8415569c7..827fa3977 100644 --- a/.github/azure-pipeline-database-ci.yml +++ b/.github/azure-pipeline-database-ci.yml @@ -1,5 +1,7 @@ trigger: - - CI + branches: + include: + - CI resources: repositories: - repository: self diff --git a/.github/azure-pipeline-openapi-reportapi-ci.yml b/.github/azure-pipeline-openapi-reportapi-ci.yml index 0446b79c2..fe44998ce 100644 --- a/.github/azure-pipeline-openapi-reportapi-ci.yml +++ b/.github/azure-pipeline-openapi-reportapi-ci.yml @@ -5,7 +5,9 @@ variables: - name: BuildParameters.projects value: '**/*.csproj' trigger: - - CI + branches: + include: + - CI name: $(date:yyyyMMdd)$(rev:.r) resources: repositories: diff --git a/.github/azure-pipeline-webui-ci.yml b/.github/azure-pipeline-webui-ci.yml index 509faf6a5..936677328 100644 --- a/.github/azure-pipeline-webui-ci.yml +++ b/.github/azure-pipeline-webui-ci.yml @@ -5,7 +5,9 @@ variables: - name: BuildParameters.TestProjects value: '**/*[Tt]ests/*.csproj' trigger: - - CI + branches: + include: + - CI name: $(date:yyyyMMdd)$(rev:.r) resources: repositories: diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Helpers/LearningActivityHelper.cs b/AdminUI/LearningHub.Nhs.AdminUI/Helpers/LearningActivityHelper.cs index 78c67b749..fc22bc3b9 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Helpers/LearningActivityHelper.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Helpers/LearningActivityHelper.cs @@ -59,6 +59,9 @@ public static string GetResourceTypeText(this MyLearningDetailedItemViewModel my case ResourceTypeEnum.Case: typeText = "Case"; break; + case ResourceTypeEnum.Html: + typeText = "HTML"; + break; default: typeText = string.Empty; break; @@ -133,6 +136,7 @@ public static string GetActivityStatusDisplayText(this MyLearningDetailedItemVie && (myLearningDetailedItemViewModel.ResourceType == ResourceTypeEnum.Article || myLearningDetailedItemViewModel.ResourceType == ResourceTypeEnum.WebLink || myLearningDetailedItemViewModel.ResourceType == ResourceTypeEnum.Image + || myLearningDetailedItemViewModel.ResourceType == ResourceTypeEnum.Html || myLearningDetailedItemViewModel.ResourceType == ResourceTypeEnum.Case)) { return "Completed"; @@ -170,6 +174,7 @@ public static ActivityStatusEnum GetActivityStatus(this MyLearningDetailedItemVi && (myLearningDetailedItemViewModel.ResourceType == ResourceTypeEnum.Article || myLearningDetailedItemViewModel.ResourceType == ResourceTypeEnum.WebLink || myLearningDetailedItemViewModel.ResourceType == ResourceTypeEnum.Image + || myLearningDetailedItemViewModel.ResourceType == ResourceTypeEnum.Html || myLearningDetailedItemViewModel.ResourceType == ResourceTypeEnum.Case)) { return ActivityStatusEnum.Completed; diff --git a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj index d302b35a4..89241ab9f 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj +++ b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj @@ -89,7 +89,7 @@ - + diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/Edit.cshtml b/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/Edit.cshtml index a53f13032..788bfaba3 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/Edit.cshtml +++ b/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/Edit.cshtml @@ -3,83 +3,83 @@ @using LearningHub.Nhs.Models.Enums @using Newtonsoft.Json @{ - var page = Model.CatalogueNodeVersionId == 0 ? CatalogueNavPage.Add : CatalogueNavPage.Edit; - ViewData["Title"] = page == CatalogueNavPage.Add ? "Add catalogue" : "Edit catalogue"; - var baseUrl = _settings.Value.LearningHubUrl + "catalogue/"; - var url = baseUrl + (Model.Url ?? ""); - var aToZ = (int)CatalogueOrder.AlphabeticalAscending; - var keywords = Model.Keywords ?? new List(); - var Providers = Model.Providers; - var CatalogueNodeVersionProviderId = Model.CatalogueNodeVersionProvider?.ProviderId ?? 0; - var CatalogueNodeVersionProvider = Model.CatalogueNodeVersionProvider; - var action = page.ToString(); - var keywordsJson = Html.Raw(JsonConvert.SerializeObject(keywords)); - var imageBaseUrl = "/file/download/CatalogueImageDirectory/"; - var lastModifiedDate = Model.LastModifiedDate?.ToString("dd MMM yyyy"); + var page = Model.CatalogueNodeVersionId == 0 ? CatalogueNavPage.Add : CatalogueNavPage.Edit; + ViewData["Title"] = page == CatalogueNavPage.Add ? "Add catalogue" : "Edit catalogue"; + var baseUrl = _settings.Value.LearningHubUrl + "catalogue/"; + var url = baseUrl + (Model.Url ?? ""); + var aToZ = (int)CatalogueOrder.AlphabeticalAscending; + var keywords = Model.Keywords ?? new List(); + var Providers = Model.Providers; + var CatalogueNodeVersionProviderId = Model.CatalogueNodeVersionProvider?.ProviderId ?? 0; + var CatalogueNodeVersionProvider = Model.CatalogueNodeVersionProvider; + var action = page.ToString(); + var keywordsJson = Html.Raw(JsonConvert.SerializeObject(keywords)); + var imageBaseUrl = "/file/download/CatalogueImageDirectory/"; + var lastModifiedDate = Model.LastModifiedDate?.ToString("dd MMM yyyy"); } @section SideMenu { - @{ - await Html.RenderPartialAsync("_NavSection"); - } + @{ + await Html.RenderPartialAsync("_NavSection"); + } } @if (!string.IsNullOrEmpty(ViewBag.ErrorMessage)) { - + } @if (page == CatalogueNavPage.Edit) { -
-
-

@Model.Name

+
+
+

@Model.Name

+
-
} @{ - await Html.RenderPartialAsync("_CatalogueNav.cshtml", new CatalogueNavViewModel { Page = page, CatalogueId = Model.CatalogueNodeVersionId }); + await Html.RenderPartialAsync("_CatalogueNav.cshtml", new CatalogueNavViewModel { Page = page, CatalogueId = Model.CatalogueNodeVersionId }); }
-
- - - - -
-
-
- @if (page == CatalogueNavPage.Edit) - { - var idString = Model.CatalogueNodeVersionId.ToString(); - var paddedIdString = idString.Length > 3 ? idString : idString.PadLeft(3, '0'); - ID: @paddedIdString - } - else - { - NEW - } + + + + + +
+
+
+ @if (page == CatalogueNavPage.Edit) + { + var idString = Model.CatalogueNodeVersionId.ToString(); + var paddedIdString = idString.Length > 3 ? idString : idString.PadLeft(3, '0'); + ID: @paddedIdString + } + else + { + NEW + } +
+ @if (page == CatalogueNavPage.Add || Model.Hidden) + { + Hidden + } + @if (lastModifiedDate != null) + { +

Last modified on: @lastModifiedDate

+ } +
- @if (page == CatalogueNavPage.Add || Model.Hidden) - { - Hidden - } - @if (lastModifiedDate != null) + + @if (!ViewData.ModelState.IsValid) { -

Last modified on: @lastModifiedDate

+
This form failed to save because there are errors below.
} -
-
- - @if (!ViewData.ModelState.IsValid) - { -
This form failed to save because there are errors below.
- }
@@ -163,269 +163,270 @@
-
-
-

You can enter a maximum of 50 characters

-
- @{ - var i = 0; - } - @foreach (var keyword in keywords) - { -
-

@keyword

- -
- } +
+

You can enter a maximum of 50 characters

+
+ @{ + var i = 0; + } + @foreach (var keyword in keywords) + { +
+

@keyword

+ + +
+ } +
+
-
-
-
+
-
-
- -
-
+
+
+ +
+
-
-
-
- @if (!Model.RestrictedAccess) - { - - } - else - { - - } - Unrestricted access (default) -
-
- @if (Model.RestrictedAccess) - { - - } - else +
+
+
+ @if (!Model.RestrictedAccess) + { + + } + else + { + + } + Unrestricted access (default) +
+
+ @if (Model.RestrictedAccess) + { + + } + else + { + + } + Restricted access +
+
+
+ @if (!Model.HasUserGroup) { - +
+

There are no user groups associated with this catalogue. You can add some in the User Groups tab.

+
} - Restricted access -
-
- @if (!Model.HasUserGroup) - { -
-

There are no user groups associated with this catalogue. You can add some in the User Groups tab.

-
- } -
-
+
-
-
- -

Give permission to allow a contributor to flag content from a specific provider. This helps to separate learning resources from community contributions.

-

Provided by;

-
-
+
+
+ +

When applicable please select the provider of this content. This will allow a contributor to flag content from a specific provider.

+

This will enable learners to search for content produced by organisations and help separate learning resources from community contributions.

+

Provided by;

+
+
-
-
- @if (Providers != null && Providers.Count() > 0) - { -
- @foreach (var provider in Providers) - { - - @provider.Name -
- } - - Not applicable +
+
+ @if (Providers != null && Providers.Count() > 0) + { +
+ @foreach (var provider in Providers) + { + + @provider.Name +
+ } + + Not applicable +
+ } +
- }
-
-
-
-
- @if (page == CatalogueNavPage.Add) - { - - } - else - { - - } -
-
- -
+
+
+ @if (page == CatalogueNavPage.Add) + { + + } + else + { + + } +
+
+ +
-@section Scripts{ - - + + } \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Controllers/Api/CatalogueController.cs b/LearningHub.Nhs.WebUI/Controllers/Api/CatalogueController.cs index 95926d55e..0a6b1b4ce 100644 --- a/LearningHub.Nhs.WebUI/Controllers/Api/CatalogueController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/Api/CatalogueController.cs @@ -133,7 +133,7 @@ public async Task GetLatestCatalogueAccessRequestAsync(int catalo [HttpPost("catalogue/RequestAccess/{reference}")] public async Task RequestAccess(string reference, CatalogueAccessRequestViewModel vm) { - return this.Ok(await this.catalogueService.RequestAccessAsync(reference, vm)); + return this.Ok(await this.catalogueService.RequestAccessAsync(reference, vm, "access")); } /// diff --git a/LearningHub.Nhs.WebUI/Controllers/Api/ContributeController.cs b/LearningHub.Nhs.WebUI/Controllers/Api/ContributeController.cs index 711f5bcf5..e86c053f8 100644 --- a/LearningHub.Nhs.WebUI/Controllers/Api/ContributeController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/Api/ContributeController.cs @@ -390,6 +390,19 @@ public async Task SaveScormDetailAsync([FromBody] ScormUpdateReque return this.Ok(resourceVersionId); } + /// + /// The SaveHtmlDetailAsync. + /// + /// Request. + /// A representing the result of the asynchronous operation. + [HttpPost] + [Route("SaveHtmlDetail")] + public async Task SaveHtmlDetailAsync([FromBody] HtmlResourceUpdateRequestViewModel request) + { + int resourceVersionId = await this.contributeService.SaveHtmlDetailAsync(request); + return this.Ok(resourceVersionId); + } + /// /// The SaveImageDetailAsync. /// diff --git a/LearningHub.Nhs.WebUI/Controllers/Api/MyLearningController.cs b/LearningHub.Nhs.WebUI/Controllers/Api/MyLearningController.cs index f6bc6d519..b25050104 100644 --- a/LearningHub.Nhs.WebUI/Controllers/Api/MyLearningController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/Api/MyLearningController.cs @@ -6,7 +6,9 @@ namespace LearningHub.Nhs.WebUI.Controllers.Api { using System.Threading.Tasks; using LearningHub.Nhs.Models.MyLearning; + using LearningHub.Nhs.WebUI.Helpers; using LearningHub.Nhs.WebUI.Interfaces; + using LearningHub.Nhs.WebUI.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -58,5 +60,27 @@ public async Task GetPlayedSegments(int resourceId, int majorVers var activityModel = await this.myLearningService.GetPlayedSegments(resourceId, majorVersion); return this.Ok(activityModel); } + + /// + /// The CheckCertificateAvailability. + /// + /// The reesource reference id. + /// A representing the result of the asynchronous operation. + [HttpGet] + [Route("CheckCertificateAvailability/{resourceReferenceId}")] + public async Task CheckCertificateAvailabilityAsync(int resourceReferenceId) + { + var certDetails = await this.myLearningService.GetResourceCertificateDetails(resourceReferenceId); + if (certDetails.Item2 != null && certDetails.Item2.IsCurrentResourceVersion) + { + var activityDetailedItemViewModel = new ActivityDetailedItemViewModel(certDetails.Item2); + if (activityDetailedItemViewModel != null && ViewActivityHelper.CanDownloadCertificate(activityDetailedItemViewModel)) + { + return true; + } + } + + return false; + } } } \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Controllers/Api/ResourceController.cs b/LearningHub.Nhs.WebUI/Controllers/Api/ResourceController.cs index 748f73c04..93c285142 100644 --- a/LearningHub.Nhs.WebUI/Controllers/Api/ResourceController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/Api/ResourceController.cs @@ -198,6 +198,18 @@ public async Task GetGenericFileDetailsByIdAsync(int id) return this.Ok(await this.resourceService.GetGenericFileDetailsByIdAsync(id)); } + /// + /// The GetHtmlDetailsByIdAsync. + /// + /// Id. + /// A representing the result of the asynchronous operation. + [HttpGet] + [Route("GetHtmlDetailsById/{id}")] + public async Task GetHtmlDetailsByIdAsync(int id) + { + return this.Ok(await this.resourceService.GetHtmlDetailsByIdAsync(id)); + } + /// /// The GetScormDetailsByIdAsync. /// @@ -217,10 +229,10 @@ public async Task GetScormDetailsByIdAsync(int id) /// id. /// A representing the result of the asynchronous operation. [HttpGet] - [Route("GetScormContentDetails/{id}")] - public async Task GetScormContentDetailsAsync(int id) + [Route("GetExternalContentDetails/{id}")] + public async Task GetExternalContentDetailsAsync(int id) { - return this.Ok(await this.resourceService.GetScormContentDetailsAsync(id)); + return this.Ok(await this.resourceService.GetExternalContentDetailsAsync(id)); } /// diff --git a/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs b/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs index 2c421d369..e3bd3b193 100644 --- a/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/CatalogueController.cs @@ -1,433 +1,560 @@ // // Copyright (c) HEE.nhs.uk. // - namespace LearningHub.Nhs.WebUI.Controllers { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Net.Http; - using System.Threading.Tasks; - using elfhHub.Nhs.Models.Entities; - using LearningHub.Nhs.Models.Catalogue; - using LearningHub.Nhs.Models.Dashboard; - using LearningHub.Nhs.Models.Enums; - using LearningHub.Nhs.Models.Hierarchy; - using LearningHub.Nhs.Models.Search; - using LearningHub.Nhs.Models.User; - using LearningHub.Nhs.WebUI.Configuration; - using LearningHub.Nhs.WebUI.Filters; - using LearningHub.Nhs.WebUI.Interfaces; - using LearningHub.Nhs.WebUI.Models; - using LearningHub.Nhs.WebUI.Models.Catalogue; - using LearningHub.Nhs.WebUI.Models.Search; - using Microsoft.AspNetCore.Authorization; - using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Mvc; - using Microsoft.AspNetCore.Routing; - using Microsoft.Extensions.Logging; - using Microsoft.Extensions.Options; - - /// - /// CatalogueController. - /// - [Authorize] - [ServiceFilter(typeof(LoginWizardFilter))] - public class CatalogueController : BaseController - { - private readonly IDashboardService dashboardService; - private readonly ISearchService searchService; - private LearningHubAuthServiceConfig authConfig; - private ICatalogueService catalogueService; - private IUserService userService; - private IUserGroupService userGroupService; - private IHierarchyService hierarchyService; - private Settings settings; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using System.Threading.Tasks; + using LearningHub.Nhs.Caching; + using LearningHub.Nhs.Models.Catalogue; + using LearningHub.Nhs.Models.Dashboard; + using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.Hierarchy; + using LearningHub.Nhs.Models.Search; + using LearningHub.Nhs.Models.User; + using LearningHub.Nhs.WebUI.Configuration; + using LearningHub.Nhs.WebUI.Filters; + using LearningHub.Nhs.WebUI.Interfaces; + using LearningHub.Nhs.WebUI.Models.Catalogue; + using LearningHub.Nhs.WebUI.Models.Search; + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.Routing; + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Options; /// - /// Initializes a new instance of the class. + /// CatalogueController. /// - /// httpClientFactory. - /// hostingEnvironment. - /// logger. - /// catalogueService. - /// userService. - /// settings. - /// authConfig. - /// searchService. - /// Dashboard service. - /// HierarchyService. - /// userGroupService. - public CatalogueController( - IHttpClientFactory httpClientFactory, - IWebHostEnvironment hostingEnvironment, - ILogger logger, - ICatalogueService catalogueService, - IUserService userService, - IOptions settings, - LearningHubAuthServiceConfig authConfig, - ISearchService searchService, - IDashboardService dashboardService, - IHierarchyService hierarchyService, - IUserGroupService userGroupService) - : base(hostingEnvironment, httpClientFactory, logger, settings.Value) + [Authorize] + [ServiceFilter(typeof(LoginWizardFilter))] + public class CatalogueController : BaseController { - this.authConfig = authConfig; - this.catalogueService = catalogueService; - this.userService = userService; - this.searchService = searchService; - this.settings = settings.Value; - this.dashboardService = dashboardService; - this.hierarchyService = hierarchyService; - this.userGroupService = userGroupService; - } + private readonly IDashboardService dashboardService; + private readonly ISearchService searchService; + private readonly ICacheService cacheService; + private LearningHubAuthServiceConfig authConfig; + private ICatalogueService catalogueService; + private IUserService userService; + private IUserGroupService userGroupService; + private IHierarchyService hierarchyService; + private Settings settings; + + /// + /// Initializes a new instance of the class. + /// + /// httpClientFactory. + /// hostingEnvironment. + /// logger. + /// catalogueService. + /// userService. + /// settings. + /// authConfig. + /// searchService. + /// cacheService. + /// Dashboard service. + /// HierarchyService. + /// userGroupService. + public CatalogueController( + IHttpClientFactory httpClientFactory, + IWebHostEnvironment hostingEnvironment, + ILogger logger, + ICatalogueService catalogueService, + IUserService userService, + IOptions settings, + LearningHubAuthServiceConfig authConfig, + ISearchService searchService, + ICacheService cacheService, + IDashboardService dashboardService, + IHierarchyService hierarchyService, + IUserGroupService userGroupService) + : base(hostingEnvironment, httpClientFactory, logger, settings.Value) + { + this.authConfig = authConfig; + this.catalogueService = catalogueService; + this.userService = userService; + this.searchService = searchService; + this.cacheService = cacheService; + this.settings = settings.Value; + this.dashboardService = dashboardService; + this.hierarchyService = hierarchyService; + this.userGroupService = userGroupService; + } - /// - /// Catalogues. - /// - /// Page index. - /// Search term. - /// IActionResult. - [Route("/catalogues")] - public async Task Index(int pageIndex = 1, string term = null) - { - if (pageIndex < 1) - { - return this.Redirect("/catalogues"); - } - - var itemsOnPage = 9; - var catalogues = new DashboardCatalogueResponseViewModel(); - - if (!string.IsNullOrWhiteSpace(term)) - { - var termCatalogues = await this.searchService.GetCatalogueSearchResultAsync( - new CatalogueSearchRequestModel + /// + /// Catalogues. + /// + /// Page index. + /// Search term. + /// IActionResult. + [Route("/catalogues")] + public async Task Index(int pageIndex = 1, string term = null) + { + if (pageIndex < 1) { - SearchText = term, - PageIndex = pageIndex - 1, - PageSize = itemsOnPage, - }); + return this.Redirect("/catalogues"); + } - catalogues.TotalCount = termCatalogues.TotalHits; - catalogues.Catalogues = termCatalogues.DocumentModel.Select(t => new DashboardCatalogueViewModel - { - Url = t.Url, - Name = t.Name, - CardImageUrl = t.CardImageUrl, - BannerUrl = t.BannerUrl, - Description = t.Description, - RestrictedAccess = t.RestrictedAccess, - HasAccess = t.HasAccess, - IsBookmarked = t.IsBookmarked, - BookmarkId = t.BookmarkId, - NodeId = int.Parse(t.Id), - BadgeUrl = t.BadgeUrl, - }).ToList(); - } - else - { - catalogues = await this.dashboardService.GetCataloguesAsync("all-catalogues", pageIndex); - } - - if (System.Math.Ceiling(catalogues.TotalCount / (decimal)itemsOnPage) < pageIndex) - { - return this.Redirect("/catalogues"); - } - - this.ViewBag.PageIndex = pageIndex; - this.ViewBag.ShowPrev = pageIndex != 1; - this.ViewBag.ShowNext = catalogues.TotalCount > itemsOnPage * pageIndex; - return this.View("Catalogues", catalogues); - } + var itemsOnPage = 9; + var catalogues = new DashboardCatalogueResponseViewModel(); - /// - /// Catalogue with authentication. - /// Forces authentication. - /// - /// Catalogue URL reference. - /// IActionResult. - [Route("CatalogueWithAuthentication/{reference}")] - public IActionResult CatalogueWithAuthentication(string reference) - { - this.ViewBag.UserAuthenticated = this.User.Identity.IsAuthenticated; - return this.Redirect($"/catalogue/{reference}"); - } + if (!string.IsNullOrWhiteSpace(term)) + { + var termCatalogues = await this.searchService.GetCatalogueSearchResultAsync( + new CatalogueSearchRequestModel + { + SearchText = term, + PageIndex = pageIndex - 1, + PageSize = itemsOnPage, + }); + + catalogues.TotalCount = termCatalogues.TotalHits; + catalogues.Catalogues = termCatalogues.DocumentModel.Select(t => new DashboardCatalogueViewModel + { + Url = t.Url, + Name = t.Name, + CardImageUrl = t.CardImageUrl, + BannerUrl = t.BannerUrl, + Description = t.Description, + RestrictedAccess = t.RestrictedAccess, + HasAccess = t.HasAccess, + IsBookmarked = t.IsBookmarked, + BookmarkId = t.BookmarkId, + NodeId = int.Parse(t.Id), + BadgeUrl = t.BadgeUrl, + }).ToList(); + } + else + { + catalogues = await this.dashboardService.GetCataloguesAsync("all-catalogues", pageIndex); + } - /// - /// Index. - /// - /// Catalogue URL reference. - /// The tab name to display. - /// The nodeId of the current folder. If not supplied, catalogue root contents are displayed. - /// The SearchRequestViewModel. - /// IActionResult. - [AllowAnonymous] - [ServiceFilter(typeof(SsoLoginFilterAttribute))] - [HttpGet] - [Route("catalogue/{reference}/{tab?}")] - public async Task IndexAsync(string reference, string tab, int? nodeId, SearchRequestViewModel search) - { - if (tab == null || (tab == "search" && !this.User.Identity.IsAuthenticated)) - { - tab = "browse"; - } - - this.ViewBag.Reference = reference; - this.ViewBag.UserAuthenticated = this.User.Identity.IsAuthenticated; - this.ViewBag.SupportUrl = this.settings.SupportUrls.SupportForm; - this.ViewBag.ShowCatalogueFieldsInResources = false; - this.ViewBag.ActiveTab = tab; - - var catalogue = await this.catalogueService.GetCatalogueAsync(reference); - - if (catalogue == null) - { - return this.RedirectToAction("Error", "Home"); - } - - var userGroups = new List(); - CatalogueAccessRequestViewModel catalogueAccessRequest = null; - if (this.ViewBag.UserAuthenticated) - { - userGroups = await this.userGroupService.GetRoleUserGroupDetailAsync(); - catalogueAccessRequest = await this.catalogueService.GetLatestCatalogueAccessRequestAsync(catalogue.NodeId); - } - - var viewModel = new CatalogueIndexViewModel() - { - Catalogue = catalogue, - UserGroups = userGroups.Where(x => x.CatalogueNodeId == catalogue.NodeId).ToList(), - CatalogueAccessRequest = catalogueAccessRequest, - }; - - if (catalogue.Hidden == true) - { - if (!(viewModel.UserGroups.Any(x => x.RoleId == (int)RoleEnum.LocalAdmin || x.RoleId == (int)RoleEnum.Editor || x.RoleId == (int)RoleEnum.Previewer) || this.User.IsInRole("Administrator"))) - { - return this.RedirectToAction("Error", "Home"); + if (System.Math.Ceiling(catalogues.TotalCount / (decimal)itemsOnPage) < pageIndex) + { + return this.Redirect("/catalogues"); + } + + this.ViewBag.PageIndex = pageIndex; + this.ViewBag.ShowPrev = pageIndex != 1; + this.ViewBag.ShowNext = catalogues.TotalCount > itemsOnPage * pageIndex; + return this.View("Catalogues", catalogues); } - } - if (tab == "browse") - { - if (nodeId.HasValue) + /// + /// Catalogue with authentication. + /// Forces authentication. + /// + /// Catalogue URL reference. + /// IActionResult. + [Route("CatalogueWithAuthentication/{reference}")] + public IActionResult CatalogueWithAuthentication(string reference) { - // if nodeId has a value it means the user is looking at a subfolder of the catalogue. - // Get the folder name and description, plus folder path data needed for the breadcrumbs. - viewModel.NodeDetails = await this.hierarchyService.GetNodeDetails(nodeId.Value); - viewModel.NodePathNodes = await this.hierarchyService.GetNodePathNodes(viewModel.NodeDetails.NodePathId); + this.ViewBag.UserAuthenticated = this.User.Identity.IsAuthenticated; + return this.Redirect($"/catalogue/{reference}"); } - else + + /// + /// Index. + /// + /// Catalogue URL reference. + /// The tab name to display. + /// The nodeId of the current folder. If not supplied, catalogue root contents are displayed. + /// The SearchRequestViewModel. + /// IActionResult. + [AllowAnonymous] + [ServiceFilter(typeof(SsoLoginFilterAttribute))] + [HttpGet] + [Route("catalogue/{reference}/{tab?}")] + public async Task IndexAsync(string reference, string tab, int? nodeId, SearchRequestViewModel search) { - // Otherwise user is looking at catalogue root. - nodeId = catalogue.NodeId; + if (tab == null || (tab == "search" && !this.User.Identity.IsAuthenticated)) + { + tab = "browse"; + } + + this.ViewBag.Reference = reference; + this.ViewBag.UserAuthenticated = this.User.Identity.IsAuthenticated; + this.ViewBag.SupportUrl = this.settings.SupportUrls.SupportForm; + this.ViewBag.ShowCatalogueFieldsInResources = false; + this.ViewBag.ActiveTab = tab; - viewModel.NodePathNodes = new List + var catalogue = await this.catalogueService.GetCatalogueAsync(reference); + + if (catalogue == null) + { + return this.RedirectToAction("Error", "Home"); + } + + var userGroups = new List(); + CatalogueAccessRequestViewModel catalogueAccessRequest = null; + if (this.ViewBag.UserAuthenticated) + { + var cacheKey = $"{this.CurrentUserId}:AllRolesWithPermissions"; + await this.cacheService.RemoveAsync(cacheKey); + userGroups = await this.userGroupService.GetRoleUserGroupDetailAsync(); + catalogueAccessRequest = await this.catalogueService.GetLatestCatalogueAccessRequestAsync(catalogue.NodeId); + } + + var viewModel = new CatalogueIndexViewModel() + { + Catalogue = catalogue, + UserGroups = userGroups.Where(x => x.CatalogueNodeId == catalogue.NodeId).ToList(), + CatalogueAccessRequest = catalogueAccessRequest, + }; + + if (catalogue.Hidden == true) + { + if (!(viewModel.UserGroups.Any(x => x.RoleId == (int)RoleEnum.LocalAdmin || x.RoleId == (int)RoleEnum.Editor || x.RoleId == (int)RoleEnum.Previewer) || this.User.IsInRole("Administrator"))) + { + if (viewModel.UserGroups.Count == 0) + { + return this.View("NoAccess", catalogue); + } + else + { + return this.View("NoUserGroupPermission", catalogue); + } + } + } + + if (tab == "browse") + { + if (nodeId.HasValue) + { + // if nodeId has a value it means the user is looking at a subfolder of the catalogue. + // Get the folder name and description, plus folder path data needed for the breadcrumbs. + viewModel.NodeDetails = await this.hierarchyService.GetNodeDetails(nodeId.Value); + viewModel.NodePathNodes = await this.hierarchyService.GetNodePathNodes(viewModel.NodeDetails.NodePathId); + } + else + { + // Otherwise user is looking at catalogue root. + nodeId = catalogue.NodeId; + + viewModel.NodePathNodes = new List { new NodeViewModel { Name = catalogue.Name }, }; - } + } - bool includeEmptyFolder = viewModel.UserGroups.Any(x => x.RoleId == (int)RoleEnum.LocalAdmin || x.RoleId == (int)RoleEnum.Editor || x.RoleId == (int)RoleEnum.Previewer) || this.User.IsInRole("Administrator"); - var nodeContents = await this.hierarchyService.GetNodeContentsForCatalogueBrowse(nodeId.Value, includeEmptyFolder); - viewModel.NodeContents = nodeContents; - } - else if (tab == "search") - { - if (viewModel.SearchResults == null) - { - viewModel.SearchResults = new Models.Search.SearchResultViewModel(); + bool includeEmptyFolder = viewModel.UserGroups.Any(x => x.RoleId == (int)RoleEnum.LocalAdmin || x.RoleId == (int)RoleEnum.Editor || x.RoleId == (int)RoleEnum.Previewer) || this.User.IsInRole("Administrator"); + var nodeContents = await this.hierarchyService.GetNodeContentsForCatalogueBrowse(nodeId.Value, includeEmptyFolder); + viewModel.NodeContents = nodeContents; + } + else if (tab == "search") + { + if (viewModel.SearchResults == null) + { + viewModel.SearchResults = new Models.Search.SearchResultViewModel(); + } + + if (search.Term != null) + { + search.CatalogueId = catalogue.NodeId; + search.SearchId ??= 0; + search.GroupId = !string.IsNullOrWhiteSpace(search.GroupId) && Guid.TryParse(search.GroupId, out Guid groupId) ? groupId.ToString() : Guid.NewGuid().ToString(); + + var searchResult = await this.searchService.PerformSearch(this.User, search); + searchResult.CatalogueId = catalogue.NodeId; + searchResult.CatalogueUrl = catalogue.Url; + if (search.SearchId == 0 && searchResult.ResourceSearchResult != null) + { + var searchId = await this.searchService.RegisterSearchEventsAsync( + search, + SearchFormActionTypeEnum.SearchWithinCatalogue, + searchResult.ResourceSearchResult.TotalHits); + + searchResult.ResourceSearchResult.SearchId = searchId; + } + + viewModel.SearchResults = searchResult; + } + } + + return this.View(viewModel); } - if (search.Term != null) + /// + /// Handles sort and filter functionality in Search tab. + /// Based on SearchController.IndexPost method. + /// + /// Catalogue URL reference. + /// The tab name to display. + /// Search object. + /// The resource result count. + /// The search filter. + /// The sort by. + /// The search group id. + /// The search id. + /// The actionResult. + [HttpPost("catalogue/{reference}/{tab?}")] + [AllowAnonymous] + public async Task IndexPost(string reference, string tab, [FromQuery] SearchRequestViewModel search, int resourceCount, [FromForm] IEnumerable filters, [FromForm] int? sortby, [FromForm] string groupId, [FromForm] int searchId) { - search.CatalogueId = catalogue.NodeId; - search.SearchId ??= 0; - search.GroupId = !string.IsNullOrWhiteSpace(search.GroupId) && Guid.TryParse(search.GroupId, out Guid groupId) ? groupId.ToString() : Guid.NewGuid().ToString(); - - var searchResult = await this.searchService.PerformSearch(this.User, search); - searchResult.CatalogueId = catalogue.NodeId; - searchResult.CatalogueUrl = catalogue.Url; - if (search.SearchId == 0 && searchResult.ResourceSearchResult != null) - { - var searchId = await this.searchService.RegisterSearchEventsAsync( - search, - SearchFormActionTypeEnum.SearchWithinCatalogue, - searchResult.ResourceSearchResult.TotalHits); - - searchResult.ResourceSearchResult.SearchId = searchId; - } - - viewModel.SearchResults = searchResult; + if (search.ResourcePageIndex > 0 && search.Filters?.Any() == true) + { + var existingFilters = search.Filters.OrderBy(t => t); + var newFilters = filters.OrderBy(t => t); + if (!newFilters.SequenceEqual(existingFilters)) + { + search.ResourcePageIndex = null; + } + } + + search.Filters = filters; + search.Sortby = sortby; + search.GroupId = groupId; + search.SearchId = searchId; + + await this.searchService.RegisterSearchEventsAsync(search, SearchFormActionTypeEnum.ApplyFilter, resourceCount); + + var routeValues = new RouteValueDictionary(search); + routeValues.Add("tab", tab); + routeValues.Add("reference", reference); + + return this.RedirectToAction("index", routeValues); } - } - return this.View(viewModel); - } - - /// - /// Handles sort and filter functionality in Search tab. - /// Based on SearchController.IndexPost method. - /// - /// Catalogue URL reference. - /// The tab name to display. - /// Search object. - /// The resource result count. - /// The search filter. - /// The sort by. - /// The search group id. - /// The search id. - /// The actionResult. - [HttpPost("catalogue/{reference}/{tab?}")] - [AllowAnonymous] - public async Task IndexPost(string reference, string tab, [FromQuery] SearchRequestViewModel search, int resourceCount, [FromForm] IEnumerable filters, [FromForm] int? sortby, [FromForm] string groupId, [FromForm] int searchId) - { - if (search.ResourcePageIndex > 0 && search.Filters?.Any() == true) - { - var existingFilters = search.Filters.OrderBy(t => t); - var newFilters = filters.OrderBy(t => t); - if (!newFilters.SequenceEqual(existingFilters)) + /// + /// Records analytics events for navigation between pages of results on the search tab. + /// Based on SearchController.RecordResourceNavigation method. + /// + /// Search object. + /// The resource result count. + /// The catalogue reference. + /// The active tab name. + /// The actionResult. + [Route("catalogue/record-search-navigation")] + public async Task RecordSearchNavigation(SearchRequestViewModel search, int resourceCount, string reference, string tab) { - search.ResourcePageIndex = null; - } - } + await this.searchService.RegisterSearchEventsAsync(search, SearchFormActionTypeEnum.ResourceNextPageChange, resourceCount); - search.Filters = filters; - search.Sortby = sortby; - search.GroupId = groupId; - search.SearchId = searchId; + var routeValues = new RouteValueDictionary(search); + routeValues.Add("tab", tab); + routeValues.Add("reference", reference); - await this.searchService.RegisterSearchEventsAsync(search, SearchFormActionTypeEnum.ApplyFilter, resourceCount); + return this.RedirectToAction("index", routeValues); + } - var routeValues = new RouteValueDictionary(search); - routeValues.Add("tab", tab); - routeValues.Add("reference", reference); + /// + /// Manage. + /// + /// Catalogue URL reference. + /// IActionResult. + [Route("catalogue/manage/{reference}")] + public IActionResult Manage(string reference) + { + this.ViewBag.Reference = reference; + this.ViewBag.UserAuthenticated = this.User.Identity.IsAuthenticated; + this.ViewBag.SupportUrl = this.settings.SupportUrls.SupportForm + "/2"; + return this.View("Manage"); + } - return this.RedirectToAction("index", routeValues); - } + /// + /// AccessRequests. + /// + /// Catalogue URL reference. + /// IActionResult. + [Route("catalogue/manage/{reference}/accessRequests")] + public IActionResult AccessRequests(string reference) + { + this.ViewBag.Reference = reference; + this.ViewBag.UserAuthenticated = this.User.Identity.IsAuthenticated; + this.ViewBag.SupportUrl = this.settings.SupportUrls.SupportForm + "/2"; + return this.View("Manage"); + } - /// - /// Records analytics events for navigation between pages of results on the search tab. - /// Based on SearchController.RecordResourceNavigation method. - /// - /// Search object. - /// The resource result count. - /// The catalogue reference. - /// The active tab name. - /// The actionResult. - [Route("catalogue/record-search-navigation")] - public async Task RecordSearchNavigation(SearchRequestViewModel search, int resourceCount, string reference, string tab) - { - await this.searchService.RegisterSearchEventsAsync(search, SearchFormActionTypeEnum.ResourceNextPageChange, resourceCount); + /// + /// AccessRequest. + /// + /// Catalogue URL reference. + /// Access request id. + /// IActionResult. + [Route("catalogue/manage/{reference}/accessRequest/{accessRequestId}")] + public IActionResult AccessRequest(string reference, string accessRequestId) + { + this.ViewBag.Reference = reference; + this.ViewBag.UserAuthenticated = this.User.Identity.IsAuthenticated; + this.ViewBag.SupportUrl = this.settings.SupportUrls.SupportForm + "/2"; + return this.View("Manage"); + } - var routeValues = new RouteValueDictionary(search); - routeValues.Add("tab", tab); - routeValues.Add("reference", reference); + /// + /// Display screen to enable user to request access to a catalogue. + /// + /// The catalogueNodeVersionId. + /// The returnUrl. + /// The . + [Route("catalogue/RequestAccess/{catalogueNodeVersionId}")] + public async Task RequestAccess(int catalogueNodeVersionId, string returnUrl = null) + { + var catalogue = await this.catalogueService.GetCatalogueAsync(catalogueNodeVersionId); + var catalogueAccessRequest = await this.catalogueService.GetLatestCatalogueAccessRequestAsync(catalogue.NodeId); + var currentUser = await this.userService.GetCurrentUserBasicDetailsAsync(); - return this.RedirectToAction("index", routeValues); - } + return this.View("RequestAccess", new CatalogueRequestAccessViewModel + { + CatalogueNodeId = catalogue.NodeId, + CatalogueName = catalogue.Name, + CatalogueUrl = catalogue.Url, + CatalogueAccessRequest = catalogueAccessRequest, + CurrentUser = currentUser, + ReturnUrl = returnUrl ?? this.Request.Headers["Referer"], + }); + } - /// - /// Manage. - /// - /// Catalogue URL reference. - /// IActionResult. - [Route("catalogue/manage/{reference}")] - public IActionResult Manage(string reference) - { - this.ViewBag.Reference = reference; - this.ViewBag.UserAuthenticated = this.User.Identity.IsAuthenticated; - this.ViewBag.SupportUrl = this.settings.SupportUrls.SupportForm + "/2"; - return this.View("Manage"); - } + /// + /// Creates a catalogue access request and displays the confirmation screen. + /// + /// The CatalogueRequestAccessViewModel. + /// The . + [HttpPost] + [Route("catalogue/RequestAccessPost")] + public async Task RequestAccess(CatalogueRequestAccessViewModel viewModel) + { + if (this.ModelState.IsValid) + { + var validationResult = await this.catalogueService.RequestAccessAsync(viewModel.CatalogueUrl, new CatalogueAccessRequestViewModel() { Message = viewModel.AccessRequestMessage, RoleId = (int)RoleEnum.Reader }, "access"); + + if (validationResult.IsValid) + { + return this.View( + "AccessRequested", + new CatalogueAccessRequestedViewModel + { + CatalogueName = viewModel.CatalogueName, + CatalogueUrl = viewModel.CatalogueUrl, + }); + } + else + { + return this.Redirect("/Home/Error"); + } + } + else + { + return this.View("RequestAccess", viewModel); + } + } - /// - /// AccessRequests. - /// - /// Catalogue URL reference. - /// IActionResult. - [Route("catalogue/manage/{reference}/accessRequests")] - public IActionResult AccessRequests(string reference) - { - this.ViewBag.Reference = reference; - this.ViewBag.UserAuthenticated = this.User.Identity.IsAuthenticated; - this.ViewBag.SupportUrl = this.settings.SupportUrls.SupportForm + "/2"; - return this.View("Manage"); - } + /// + /// Display screen to enable user to request access to a catalogue. + /// + /// The catalogueNodeVersionId. + /// The returnUrl. + /// The . + [Route("catalogue/RequestPermission/{catalogueNodeVersionId}")] + public async Task RequestPermission(int catalogueNodeVersionId, string returnUrl = null) + { + var catalogue = await this.catalogueService.GetCatalogueAsync(catalogueNodeVersionId); + var catalogueAccessRequest = await this.catalogueService.GetLatestCatalogueAccessRequestAsync(catalogue.NodeId); + var currentUser = await this.userService.GetCurrentUserBasicDetailsAsync(); - /// - /// AccessRequest. - /// - /// Catalogue URL reference. - /// Access request id. - /// IActionResult. - [Route("catalogue/manage/{reference}/accessRequest/{accessRequestId}")] - public IActionResult AccessRequest(string reference, string accessRequestId) - { - this.ViewBag.Reference = reference; - this.ViewBag.UserAuthenticated = this.User.Identity.IsAuthenticated; - this.ViewBag.SupportUrl = this.settings.SupportUrls.SupportForm + "/2"; - return this.View("Manage"); - } + return this.View("RequestPermission", new CatalogueRequestAccessViewModel + { + CatalogueNodeId = catalogue.NodeId, + CatalogueName = catalogue.Name, + CatalogueUrl = catalogue.Url, + CatalogueAccessRequest = catalogueAccessRequest, + CurrentUser = currentUser, + ReturnUrl = returnUrl ?? this.Request.Headers["Referer"], + }); + } - /// - /// Display screen to enable user to request access to a catalogue. - /// - /// The catalogueNodeVersionId. - /// The returnUrl. - /// The . - [Route("catalogue/RequestAccess/{catalogueNodeVersionId}")] - public async Task RequestAccess(int catalogueNodeVersionId, string returnUrl = null) - { - var catalogue = await this.catalogueService.GetCatalogueAsync(catalogueNodeVersionId); - var catalogueAccessRequest = await this.catalogueService.GetLatestCatalogueAccessRequestAsync(catalogue.NodeId); - var currentUser = await this.userService.GetCurrentUserBasicDetailsAsync(); - - return this.View("RequestAccess", new CatalogueRequestAccessViewModel - { - CatalogueNodeId = catalogue.NodeId, - CatalogueName = catalogue.Name, - CatalogueUrl = catalogue.Url, - CatalogueAccessRequest = catalogueAccessRequest, - CurrentUser = currentUser, - ReturnUrl = returnUrl ?? this.Request.Headers["Referer"], - }); - } + /// + /// Display screen to enable user to request access to a catalogue. + /// + /// The catalogueNodeVersionId. + /// The returnUrl. + /// The . + [Route("catalogue/RequestPreviewAccess/{catalogueNodeVersionId}")] + public async Task RequestPreviewAccess(int catalogueNodeVersionId, string returnUrl = null) + { + var catalogue = await this.catalogueService.GetCatalogueAsync(catalogueNodeVersionId); + var catalogueAccessRequest = await this.catalogueService.GetLatestCatalogueAccessRequestAsync(catalogue.NodeId); + var currentUser = await this.userService.GetCurrentUserBasicDetailsAsync(); - /// - /// Creates a catalogue access request and displays the confirmation screen. - /// - /// The CatalogueRequestAccessViewModel. - /// The . - [HttpPost] - [Route("catalogue/RequestAccessPost")] - public async Task RequestAccess(CatalogueRequestAccessViewModel viewModel) - { - if (this.ModelState.IsValid) - { - var validationResult = await this.catalogueService.RequestAccessAsync(viewModel.CatalogueUrl, new CatalogueAccessRequestViewModel() { Message = viewModel.AccessRequestMessage }); + return this.View("RequestPreviewAccess", new CatalogueRequestAccessViewModel + { + CatalogueNodeId = catalogue.NodeId, + CatalogueName = catalogue.Name, + CatalogueUrl = catalogue.Url, + CatalogueAccessRequest = catalogueAccessRequest, + CurrentUser = currentUser, + ReturnUrl = returnUrl ?? this.Request.Headers["Referer"], + }); + } - if (validationResult.IsValid) + /// + /// Creates a catalogue access request and displays the confirmation screen. + /// + /// The CatalogueRequestAccessViewModel. + /// The . + [HttpPost] + [Route("catalogue/RequestPermissionPost")] + public async Task RequestPermission(CatalogueRequestAccessViewModel viewModel) { - return this.View( - "AccessRequested", - new CatalogueAccessRequestedViewModel - { - CatalogueName = viewModel.CatalogueName, - CatalogueUrl = viewModel.CatalogueUrl, - }); + if (this.ModelState.IsValid) + { + var validationResult = await this.catalogueService.RequestAccessAsync(viewModel.CatalogueUrl, new CatalogueAccessRequestViewModel() { Message = viewModel.AccessRequestMessage, RoleId = (int)RoleEnum.Previewer }, "permission"); + + if (validationResult.IsValid) + { + return this.View( + "PermissionRequested", + new CatalogueAccessRequestedViewModel + { + CatalogueName = viewModel.CatalogueName, + CatalogueUrl = viewModel.CatalogueUrl, + }); + } + else + { + return this.Redirect("/Home/Error"); + } + } + else + { + return this.View("RequestPermission", viewModel); + } } - else + + /// + /// Creates a catalogue access request and displays the confirmation screen. + /// + /// The CatalogueRequestAccessViewModel. + /// The . + [HttpPost] + [Route("catalogue/RequestPreviewAccessPost")] + public async Task RequestPreviewAccess(CatalogueRequestAccessViewModel viewModel) { - return this.Redirect("/Home/Error"); + if (this.ModelState.IsValid) + { + var validationResult = await this.catalogueService.RequestAccessAsync(viewModel.CatalogueUrl, new CatalogueAccessRequestViewModel() { Message = viewModel.AccessRequestMessage, RoleId = (int)RoleEnum.Previewer }, "access"); + + if (validationResult.IsValid) + { + return this.View( + "PreviewAccessRequested", + new CatalogueAccessRequestedViewModel + { + CatalogueName = viewModel.CatalogueName, + CatalogueUrl = viewModel.CatalogueUrl, + }); + } + else + { + return this.Redirect("/Home/Error"); + } + } + else + { + return this.View("RequestPreviewAccess", viewModel); + } } - } - else - { - return this.View("RequestAccess", viewModel); - } } - } -} +} \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Controllers/ContributeController.cs b/LearningHub.Nhs.WebUI/Controllers/ContributeController.cs index 8f8f0ad66..ca9604e0b 100644 --- a/LearningHub.Nhs.WebUI/Controllers/ContributeController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/ContributeController.cs @@ -114,7 +114,7 @@ public async Task ContributeAResource() } else { - this.ViewBag.ResourceTypesSupported = "1,2,3,6"; + this.ViewBag.ResourceTypesSupported = "1,2,3,6,12"; } return this.View(); diff --git a/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs b/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs index 4862dbb6a..ba8813502 100644 --- a/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/MyAccountController.cs @@ -817,7 +817,7 @@ await this.userService.UpdateUserEmployment( LocationId = profile.LocationId, }); - this.ViewBag.SuccessMessage = "Your job details has been changed"; + this.ViewBag.SuccessMessage = "Your job details have been changed"; return this.View("SuccessMessage"); } diff --git a/LearningHub.Nhs.WebUI/Controllers/MyLearningController.cs b/LearningHub.Nhs.WebUI/Controllers/MyLearningController.cs index fd62173ca..2d77c7df5 100644 --- a/LearningHub.Nhs.WebUI/Controllers/MyLearningController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/MyLearningController.cs @@ -13,6 +13,7 @@ namespace LearningHub.Nhs.WebUI.Controllers using LearningHub.Nhs.Models.MyLearning; using LearningHub.Nhs.Models.Report; using LearningHub.Nhs.WebUI.Configuration; + using LearningHub.Nhs.WebUI.Extensions; using LearningHub.Nhs.WebUI.Filters; using LearningHub.Nhs.WebUI.Helpers; using LearningHub.Nhs.WebUI.Interfaces; @@ -125,6 +126,7 @@ public async Task Index(MyLearningViewModel learningRequest = nul Image = learningRequest.Image, Audio = learningRequest.Audio, Elearning = learningRequest.Elearning, + Html = learningRequest.Html, Assessment = learningRequest.Assessment, Complete = learningRequest.Complete, Incomplete = learningRequest.Incomplete, @@ -478,7 +480,7 @@ public async Task DownloadCertificate(int resourceReferenceId, in var pdfReportFile = await this.pdfReportService.GetPdfReportFile(pdfReportResponse); if (pdfReportFile != null) { - var fileName = "LearningCertificate.pdf"; + string fileName = this.GenerateCertificateName(certificateDetails.ActivityDetailedItemViewModel.Title); return this.File(pdfReportFile, FileHelper.GetContentTypeFromFileName(fileName), fileName); } } @@ -486,5 +488,29 @@ public async Task DownloadCertificate(int resourceReferenceId, in return this.View("LearningCertificate", certificateDetails); } + + /// + /// Gets the Certificate name. + /// + /// The resourceTitile. + /// The . + private string GenerateCertificateName(string resourceTitile) + { + if (!string.IsNullOrEmpty(resourceTitile)) + { + if (resourceTitile.Length <= 71) + { + string filename = "LH_Certificate_" + resourceTitile + ".pdf"; + return filename; + } + else if (resourceTitile.Length > 71) + { + string filename = "LH_Certificate_" + resourceTitile.Truncate(67, true) + "_ " + ".pdf"; + return filename; + } + } + + return "LearningCertificate.pdf"; + } } } \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Controllers/ResourceController.cs b/LearningHub.Nhs.WebUI/Controllers/ResourceController.cs index b040c2cf8..629f61c26 100644 --- a/LearningHub.Nhs.WebUI/Controllers/ResourceController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/ResourceController.cs @@ -8,10 +8,12 @@ namespace LearningHub.Nhs.WebUI.Controllers using System.Linq; using System.Net.Http; using System.Threading.Tasks; + using LearningHub.Nhs.Caching; using LearningHub.Nhs.Models.Common; using LearningHub.Nhs.Models.Entities.Hierarchy; using LearningHub.Nhs.Models.Entities.Resource; using LearningHub.Nhs.Models.Enums; + using LearningHub.Nhs.Models.Extensions; using LearningHub.Nhs.Models.Resource; using LearningHub.Nhs.Models.Resource.Activity; using LearningHub.Nhs.Models.Validation; @@ -24,6 +26,7 @@ namespace LearningHub.Nhs.WebUI.Controllers using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; + using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -42,6 +45,8 @@ public class ResourceController : BaseController private readonly ICatalogueService catalogueService; private readonly IHierarchyService hierarchyService; private readonly IMyLearningService myLearningService; + private readonly IFileService fileService; + private readonly ICacheService cacheService; /// /// Initializes a new instance of the class. @@ -59,6 +64,8 @@ public class ResourceController : BaseController /// The catalogueService. /// The myLearningService. /// The hierarchyService. + /// The fileService. + /// The cacheService. public ResourceController( IWebHostEnvironment hostingEnvironment, ILogger logger, @@ -72,7 +79,9 @@ public ResourceController( IActivityService activityService, ICatalogueService catalogueService, IMyLearningService myLearningService, - IHierarchyService hierarchyService) + IHierarchyService hierarchyService, + IFileService fileService, + ICacheService cacheService) : base(hostingEnvironment, httpClientFactory, logger, settings.Value) { this.azureMediaService = azureMediaService; @@ -84,6 +93,8 @@ public ResourceController( this.catalogueService = catalogueService; this.hierarchyService = hierarchyService; this.myLearningService = myLearningService; + this.fileService = fileService; + this.cacheService = cacheService; } /// @@ -124,10 +135,12 @@ public async Task Index(int resourceReferenceId, bool? acceptSens var catalogueAccessRequest = await this.catalogueService.GetLatestCatalogueAccessRequestAsync(resource.Catalogue.NodeId); - ScormContentDetailsViewModel scormContentDetails = null; - if (resource.ResourceTypeEnum == ResourceTypeEnum.Scorm) + ExternalContentDetailsViewModel externalContentDetails = null; + if (resource.ResourceTypeEnum == ResourceTypeEnum.Scorm + || resource.ResourceTypeEnum == ResourceTypeEnum.GenericFile + || resource.ResourceTypeEnum == ResourceTypeEnum.Html) { - scormContentDetails = await this.resourceService.GetScormContentDetailsAsync(resource.ResourceVersionId); + externalContentDetails = await this.resourceService.GetExternalContentDetailsAsync(resource.ResourceVersionId); } var hasCatalogueAccess = false; @@ -199,7 +212,7 @@ public async Task Index(int resourceReferenceId, bool? acceptSens ResourceReferenceId = resourceReferenceId, ResourceItem = resource, ResourceRating = resourceRating, - ScormContentDetails = scormContentDetails, + ExternalContentDetails = externalContentDetails, HasCatalogueAccess = hasCatalogueAccess, UserHasCertificate = userHasCertificate, CatalogueAccessRequest = catalogueAccessRequest, @@ -418,5 +431,78 @@ public async Task UnpublishConfirm(ResourceUnpublishConfirmViewMo return this.Redirect("/Home/Error"); } } + + /// + /// View HTML resource content. + /// + /// Resource reference id. + /// Html resource content relative path. + /// The file content. + [HttpGet] + [Authorize] + [Route("resource/html/{resourceReferenceId}/{*path}")] + public async Task HtmlResourceContent(int resourceReferenceId, string path) + { + if (resourceReferenceId == 0 || string.IsNullOrWhiteSpace(path)) + { + return this.Redirect("/Home/Error"); + } + + var userId = this.User.Identity.GetCurrentUserId(); + var cacheKey = $"HtmlContent:{userId}:{resourceReferenceId}"; + var (cacheExists, cacheValue) = await this.cacheService.TryGetAsync(cacheKey); + + if (!cacheExists) + { + var resource = await this.resourceService.GetItemByIdAsync(resourceReferenceId); + + if (resource == null || resource.Id == 0 || (resource.Catalogue != null && resource.Catalogue.Hidden)) + { + this.ViewBag.SupportFormUrl = this.Settings.SupportUrls.SupportForm; + return this.View("Unavailable"); + } + + if (resource.VersionStatusEnum == VersionStatusEnum.Unpublished && !resource.DisplayForContributor) + { + return this.RedirectToAction("unpublished"); + } + + cacheValue = $"{resource.ResourceVersionId}:{resource.NodePathId}:{resource.HtmlDetails.ContentFilePath}"; + + await this.cacheService.SetAsync(cacheKey, cacheValue); + } + + var splits = cacheValue.Split(":"); + var resourceVersionId = int.Parse(splits[0]); + var nodePathId = int.Parse(splits[1]); + var contentFilePath = splits[2]; + + if (path?.ToLower() == "index.html") + { + var activity = new CreateResourceActivityViewModel + { + ResourceVersionId = resourceVersionId, + NodePathId = nodePathId, + ActivityStart = DateTime.UtcNow, // TODO: What about user's timezone offset when Javascript is disabled? Needs JavaScript. + ActivityStatus = ActivityStatusEnum.Launched, + }; + await this.activityService.CreateResourceActivityAsync(activity); + } + + if (!new FileExtensionContentTypeProvider().TryGetContentType(path, out string contentType)) + { + contentType = "text/html"; + } + + var file = await this.fileService.DownloadFileAsync(contentFilePath, path); + if (file != null) + { + return this.File(file.Content, contentType); + } + else + { + return this.Ok(this.Content("No file found")); + } + } } -} +} \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Helpers/UtilityHelper.cs b/LearningHub.Nhs.WebUI/Helpers/UtilityHelper.cs index 0a6b90df7..f7963a49e 100644 --- a/LearningHub.Nhs.WebUI/Helpers/UtilityHelper.cs +++ b/LearningHub.Nhs.WebUI/Helpers/UtilityHelper.cs @@ -33,6 +33,7 @@ public static class UtilityHelper { "assessment", ResourceTypeEnum.Assessment }, { "genericfile", ResourceTypeEnum.GenericFile }, { "image", ResourceTypeEnum.Image }, + { "html", ResourceTypeEnum.Html }, }; /// @@ -100,6 +101,8 @@ public static string GetResourceTypeIconClass(ResourceTypeEnum resourceType) return "fa-solid fa-globe"; case ResourceTypeEnum.Case: return "fa-solid fa-microscope"; + case ResourceTypeEnum.Html: + return "fa-solid fa-code"; default: return "fa-regular fa-file"; } @@ -137,6 +140,8 @@ public static string GetPrettifiedResourceTypeName(ResourceTypeEnum resourceType return "Embedded"; case ResourceTypeEnum.Case: return "Case"; + case ResourceTypeEnum.Html: + return "HTML"; default: return "File"; } @@ -173,6 +178,8 @@ public static string GetPrettifiedResourceTypeName(ResourceTypeEnum resourceType return "Embedded"; case ResourceTypeEnum.Case: return "Case"; + case ResourceTypeEnum.Html: + return "HTML"; default: return "File"; } @@ -345,11 +352,11 @@ public static T ToEnum(this string value, bool ignoreCase = true) public static string GetAuthoredDate(int? day, int? month, int? year) { var authoredDate = string.Empty; - if (year.HasValue) + if (year.HasValue && year != 0) { authoredDate = year.Value.ToString(); - if (month.HasValue) + if (month.HasValue && month != 0) { var monthName = CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(month.Value); authoredDate = $"{monthName} {authoredDate}"; diff --git a/LearningHub.Nhs.WebUI/Helpers/ViewActivityHelper.cs b/LearningHub.Nhs.WebUI/Helpers/ViewActivityHelper.cs index 52ec9fdf3..a04b58c4d 100644 --- a/LearningHub.Nhs.WebUI/Helpers/ViewActivityHelper.cs +++ b/LearningHub.Nhs.WebUI/Helpers/ViewActivityHelper.cs @@ -58,6 +58,9 @@ public static string GetResourceTypeText(this ActivityDetailedItemViewModel acti case ResourceTypeEnum.Case: typeText = "Case"; break; + case ResourceTypeEnum.Html: + typeText = "HTML"; + break; default: typeText = string.Empty; break; @@ -132,6 +135,7 @@ public static string GetActivityStatusDisplayText(this ActivityDetailedItemViewM && (activityDetailedItemViewModel.ResourceType == ResourceTypeEnum.Article || activityDetailedItemViewModel.ResourceType == ResourceTypeEnum.WebLink || activityDetailedItemViewModel.ResourceType == ResourceTypeEnum.Image + || activityDetailedItemViewModel.ResourceType == ResourceTypeEnum.Html || activityDetailedItemViewModel.ResourceType == ResourceTypeEnum.Case)) { return "Completed"; @@ -169,6 +173,7 @@ public static ActivityStatusEnum GetActivityStatus(this ActivityDetailedItemView && (activityDetailedItemViewModel.ResourceType == ResourceTypeEnum.Article || activityDetailedItemViewModel.ResourceType == ResourceTypeEnum.WebLink || activityDetailedItemViewModel.ResourceType == ResourceTypeEnum.Image + || activityDetailedItemViewModel.ResourceType == ResourceTypeEnum.Html || activityDetailedItemViewModel.ResourceType == ResourceTypeEnum.Case)) { return ActivityStatusEnum.Completed; diff --git a/LearningHub.Nhs.WebUI/Interfaces/ICatalogueService.cs b/LearningHub.Nhs.WebUI/Interfaces/ICatalogueService.cs index b00b33227..094ad4810 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/ICatalogueService.cs +++ b/LearningHub.Nhs.WebUI/Interfaces/ICatalogueService.cs @@ -74,8 +74,9 @@ public interface ICatalogueService /// /// The catalogue reference. /// The view model. + /// The accessType. /// The task. - Task RequestAccessAsync(string reference, CatalogueAccessRequestViewModel vm); + Task RequestAccessAsync(string reference, CatalogueAccessRequestViewModel vm, string accessType); /// /// The InviteUserAsync. diff --git a/LearningHub.Nhs.WebUI/Interfaces/IContributeService.cs b/LearningHub.Nhs.WebUI/Interfaces/IContributeService.cs index 50d5427eb..f08da24f9 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/IContributeService.cs +++ b/LearningHub.Nhs.WebUI/Interfaces/IContributeService.cs @@ -166,6 +166,13 @@ public interface IContributeService /// A representing the result of the asynchronous operation. Task SaveScormDetailAsync(ScormUpdateRequestViewModel model); + /// + /// The SaveHtmlDetailAsync. + /// + /// Model. + /// A representing the result of the asynchronous operation. + Task SaveHtmlDetailAsync(HtmlResourceUpdateRequestViewModel model); + /// /// The SaveImageDetailAsync. /// diff --git a/LearningHub.Nhs.WebUI/Interfaces/IResourceService.cs b/LearningHub.Nhs.WebUI/Interfaces/IResourceService.cs index 9ffd77d9c..043deaa83 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/IResourceService.cs +++ b/LearningHub.Nhs.WebUI/Interfaces/IResourceService.cs @@ -59,6 +59,13 @@ public interface IResourceService /// A representing the result of the asynchronous operation. Task GetGenericFileDetailsByIdAsync(int resourceVersionId); + /// + /// The GetHtmlDetailsByIdAsync. + /// + /// Resource version id. + /// A representing the result of the asynchronous operation. + Task GetHtmlDetailsByIdAsync(int resourceVersionId); + /// /// The GetScormDetailsByIdAsync. /// @@ -67,11 +74,11 @@ public interface IResourceService Task GetScormDetailsByIdAsync(int resourceVersionId); /// - /// The GetScormContentDetailsAsync. + /// The GetExternalContentDetailsAsync. /// /// resourceVersionId. /// A representing the result of the asynchronous operation. - Task GetScormContentDetailsAsync(int resourceVersionId); + Task GetExternalContentDetailsAsync(int resourceVersionId); /// /// The RecordExternalReferenceUserAgreementAsync. diff --git a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj index 8dcadc93d..16161f081 100644 --- a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj +++ b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj @@ -28,6 +28,10 @@ + + + + @@ -50,6 +54,7 @@ + @@ -107,7 +112,7 @@ - + @@ -121,7 +126,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/LearningHub.Nhs.WebUI/Models/MyLearningViewModel.cs b/LearningHub.Nhs.WebUI/Models/MyLearningViewModel.cs index 797352a0a..14da105e7 100644 --- a/LearningHub.Nhs.WebUI/Models/MyLearningViewModel.cs +++ b/LearningHub.Nhs.WebUI/Models/MyLearningViewModel.cs @@ -211,6 +211,7 @@ public List TypeFilterCheckbox() new CheckboxListItemViewModel("Case", "Case", null), new CheckboxListItemViewModel("Elearning", "elearning", null), new CheckboxListItemViewModel("File", "File", null), + new CheckboxListItemViewModel("Html", "HTML", null), new CheckboxListItemViewModel("Image", "Image", null), new CheckboxListItemViewModel("Video", "Video", null), new CheckboxListItemViewModel("Weblink", "Weblink", null), diff --git a/LearningHub.Nhs.WebUI/Models/Resource/ResourceIndexViewModel.cs b/LearningHub.Nhs.WebUI/Models/Resource/ResourceIndexViewModel.cs index f8089e84c..c7f98b21f 100644 --- a/LearningHub.Nhs.WebUI/Models/Resource/ResourceIndexViewModel.cs +++ b/LearningHub.Nhs.WebUI/Models/Resource/ResourceIndexViewModel.cs @@ -33,7 +33,7 @@ public class ResourceIndexViewModel /// /// Gets or sets the ScormContentDetails. /// - public ScormContentDetailsViewModel ScormContentDetails { get; set; } + public ExternalContentDetailsViewModel ExternalContentDetails { get; set; } /// /// Gets or sets a value indicating whether the user has access to the catalogue that the resource is located in. diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/catalogue/catalogueaccessrequest.vue b/LearningHub.Nhs.WebUI/Scripts/vuesrc/catalogue/catalogueaccessrequest.vue index c61735ab7..1404eba89 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/catalogue/catalogueaccessrequest.vue +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/catalogue/catalogueaccessrequest.vue @@ -1,40 +1,40 @@  diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/Content.vue b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/Content.vue index 0cf20e9f8..63ca01caf 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/Content.vue +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/Content.vue @@ -60,6 +60,10 @@ Web link
If you have a link to a website that you want to share, for example https://www.nhs.uk

+

+ HTML
+ If you want to add an HTML resource package in a zip file. +

@@ -143,6 +147,11 @@ + + @@ -169,7 +178,7 @@ +
+
+

ESR learning object link

+

Select an option below.

+
+
+
+ +
+
+

+ Electronic Staff Record (ESR) users with the Learning Administration User Responsibility Profile (URP) + can set up elearning content to play via ESR. Providing a link enables users with that URP to add this resource as a + learning object in ESR. Select one of the three options to indicate if / how you would like the ESR link to be displayed + to other users in the Learning Hub. +

+
+
+
+
+
+
+ + + + +
+
+
+ + + @@ -94,6 +177,7 @@ import LhDatePicker from '../datepicker.vue'; import store from './contributeState'; import FilePanel from './FilePanel.vue'; + import { ResourceType } from '../constants'; const month_in_past = (value: number) => { if (!value) { return true; } @@ -130,7 +214,11 @@ authoredYear: '' as string, authoredMonth: '' as string, authoredDayOfMonth: '' as string, - additionalInformation: '' as string + additionalInformation: '' as string, + previousEsrLinkType: 1 as number, + showEsrModal: false as Boolean, + fieldName: null as string, + changedValue: null as string }; }, computed: { @@ -144,7 +232,8 @@ return this.$store.state.fileUpdated; }, scormOption(): boolean { - return this.localGenericFileDetail.file.fileName.endsWith('.zip'); + return this.localGenericFileDetail.file.fileName.endsWith('.zip') + && store.state.resourceDetail.resourceType != ResourceType.HTML; }, currentYear(): number { let dt = new Date(); @@ -167,10 +256,21 @@ let day = '0' + dt.getDate().toString(); return day.substring(day.length - 2); } + }, + resourceCatalogueCount(): number { + if (!this.$store.state.userCatalogues) { + return 0; + } else { + return this.$store.state.userCatalogues.length; + } + }, + previousVersionExists(): boolean { + return this.$store.state.previousVersionExists; } }, created() { this.localGenericFileDetail = _.cloneDeep(this.genericFileDetail); + this.previousEsrLinkType = this.localGenericFileDetail.esrLinkType; if (this.localGenericFileDetail.authoredYear) { this.authoredYear = this.localGenericFileDetail.authoredYear.toString(); } @@ -188,6 +288,33 @@ changeFile() { this.$emit('filechanged'); }, + showWarningModal(field: string, value: string) { + this.fieldName = field; + this.changedValue = value + if (!this.previousVersionExists || this.previousEsrLinkType <= (+value)) { + this.processChangeEsrLinkType(); + } + else { + this.showEsrModal = true + } + }, + hideWarningModal() { + this.localGenericFileDetail.esrLinkType = this.previousEsrLinkType; + this.showEsrModal = false; + }, + processChangeEsrLinkType() { + let preventSave = false; + // "this.genericFileDetail[field as keyof GenericFileResourceModel]" equivalent to "this.genericFileDetail[field]" + // TypeScript syntax is needed because noImplicitAny is set to true in the tsconfig.json file + let storedValue: string = ''; + if (this.genericFileDetail[this.fieldName as keyof GenericFileResourceModel] != null) { + storedValue = this.genericFileDetail[this.fieldName as keyof GenericFileResourceModel].toString(); + } + if (!preventSave && storedValue != this.changedValue) { + this.$store.commit("saveGenericFileDetail", { field: this.fieldName, value: this.changedValue }); + } + this.showEsrModal = false; + }, setProperty(field: string, value: string) { let preventSave = false; switch (field) { @@ -260,3 +387,26 @@ }) + \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/ContentHtml.vue b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/ContentHtml.vue new file mode 100644 index 000000000..9927f4530 --- /dev/null +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/ContentHtml.vue @@ -0,0 +1,390 @@ + + + + + diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/GenericFileUploader.vue b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/GenericFileUploader.vue index ce1518122..11e2d1fab 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/GenericFileUploader.vue +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/GenericFileUploader.vue @@ -8,7 +8,7 @@
No file chosen - +
@@ -16,28 +16,30 @@