diff --git a/Disco.Services/Documents/DocumentTemplateExtensions.cs b/Disco.Services/Documents/DocumentTemplateExtensions.cs index 5db386ee..bd3fee40 100644 --- a/Disco.Services/Documents/DocumentTemplateExtensions.cs +++ b/Disco.Services/Documents/DocumentTemplateExtensions.cs @@ -149,27 +149,28 @@ namespace Disco.Services template = database.DocumentTemplates.Find(templateId); if (template == null) throw new ArgumentException("Invalid document template id", nameof(templateId)); + if (!Enum.TryParse(template.Scope, out AttachmentTypes scope)) + throw new InvalidOperationException("Unknown DocumentType Scope"); // validate authorization - switch (template.Scope) + switch (scope) { - case DocumentTemplate.DocumentTemplateScopes.Device: + case AttachmentTypes.Device: authorization.Require(Claims.Device.Actions.GenerateDocuments); break; - case DocumentTemplate.DocumentTemplateScopes.Job: + case AttachmentTypes.Job: authorization.Require(Claims.Job.Actions.GenerateDocuments); break; - case DocumentTemplate.DocumentTemplateScopes.User: + case AttachmentTypes.User: authorization.Require(Claims.User.Actions.GenerateDocuments); break; default: - throw new InvalidOperationException("Unknown DocumentType Scope"); + throw new InvalidOperationException("Unsupported DocumentType Scope"); } // resolve target - target = template.ResolveScopeTarget(database, targetId, out targetUser); - if (target == null) - throw new ArgumentException("Target not found", nameof(targetId)); + target = template.ResolveScopeTarget(database, targetId, out targetUser) + ?? throw new ArgumentException("Target not found", nameof(targetId)); } public static IEnumerable GetOnImportUserFlagRuleDetails(this DocumentTemplate template, DiscoDataContext database) diff --git a/Disco.Services/Documents/DocumentTemplatePackageExtensions.cs b/Disco.Services/Documents/DocumentTemplatePackageExtensions.cs index c0cc8f7d..4c849eb5 100644 --- a/Disco.Services/Documents/DocumentTemplatePackageExtensions.cs +++ b/Disco.Services/Documents/DocumentTemplatePackageExtensions.cs @@ -1,6 +1,8 @@ using Disco.Data.Repository; using Disco.Models.Repository; using Disco.Models.Services.Documents; +using Disco.Services.Authorization; +using Disco.Services.Documents; using Disco.Services.Expressions; using System; using System.Collections.Generic; @@ -139,12 +141,46 @@ namespace Disco.Services } public static IAttachmentTarget ResolveScopeTarget(this DocumentTemplatePackage templatePackage, DiscoDataContext database, string targetId) + => templatePackage.ResolveScopeTarget(database, targetId, out _); + + public static IAttachmentTarget ResolveScopeTarget(this DocumentTemplatePackage templatePackage, DiscoDataContext database, string targetId, out User targetUser) { if (templatePackage == null) throw new ArgumentNullException(nameof(templatePackage)); - return templatePackage.Scope.ResolveScopeTarget(database, targetId); + return templatePackage.Scope.ResolveScopeTarget(database, targetId, out targetUser); } + public static void GetPackageAndTarget(DiscoDataContext database, AuthorizationToken authorization, string packageId, string targetId, out DocumentTemplatePackage package, out IAttachmentTarget target, out User targetUser) + { + if (string.IsNullOrWhiteSpace(packageId)) + throw new ArgumentNullException(nameof(packageId)); + if (string.IsNullOrWhiteSpace(targetId)) + throw new ArgumentNullException(nameof(targetId)); + + // get template + package = DocumentTemplatePackages.GetPackage(packageId) + ?? throw new ArgumentException("Invalid document template package id", nameof(packageId)); + + // validate authorization + switch (package.Scope) + { + case AttachmentTypes.Device: + authorization.Require(Claims.Device.Actions.GenerateDocuments); + break; + case AttachmentTypes.Job: + authorization.Require(Claims.Job.Actions.GenerateDocuments); + break; + case AttachmentTypes.User: + authorization.Require(Claims.User.Actions.GenerateDocuments); + break; + default: + throw new InvalidOperationException("Unsupported DocumentType Scope"); + } + + // resolve target + target = package.ResolveScopeTarget(database, targetId, out targetUser) + ?? throw new ArgumentException("Target not found", nameof(targetId)); + } } } diff --git a/Disco.Web/Areas/API/Controllers/DocumentTemplateController.cs b/Disco.Web/Areas/API/Controllers/DocumentTemplateController.cs index afad8b29..68f18345 100644 --- a/Disco.Web/Areas/API/Controllers/DocumentTemplateController.cs +++ b/Disco.Web/Areas/API/Controllers/DocumentTemplateController.cs @@ -1961,9 +1961,9 @@ namespace Disco.Web.Areas.API.Controllers #region Handlers [HttpPost, ValidateAntiForgeryToken] - public virtual ActionResult GenerateDocumentHandlerUi(string templateId, string targetId, string handlerId) + public virtual ActionResult GenerateDocumentHandlerUi(string id, string targetId, string handlerId) { - Disco.Services.DocumentTemplateExtensions.GetTemplateAndTarget(Database, Authorization, templateId, targetId, out var template, out var target, out var targetUser); + Disco.Services.DocumentTemplateExtensions.GetTemplateAndTarget(Database, Authorization, id, targetId, out var template, out var target, out var targetUser); var handlerManifest = Plugins.GetPluginFeature(handlerId, typeof(DocumentHandlerProviderFeature)); @@ -1984,9 +1984,9 @@ namespace Disco.Web.Areas.API.Controllers } [HttpPost, ValidateAntiForgeryToken] - public virtual ActionResult DocumentHandlers(string templateId, string targetId) + public virtual ActionResult DocumentHandlers(string id, string targetId) { - Disco.Services.DocumentTemplateExtensions.GetTemplateAndTarget(Database, Authorization, templateId, targetId, out var template, out var target, out _); + Disco.Services.DocumentTemplateExtensions.GetTemplateAndTarget(Database, Authorization, id, targetId, out var template, out var target, out _); var handlers = Plugins.GetPluginFeatures(typeof(DocumentHandlerProviderFeature)) .SelectMany(f => diff --git a/Disco.Web/Areas/API/Controllers/DocumentTemplatePackageController.cs b/Disco.Web/Areas/API/Controllers/DocumentTemplatePackageController.cs index 5445358e..ed21f12d 100644 --- a/Disco.Web/Areas/API/Controllers/DocumentTemplatePackageController.cs +++ b/Disco.Web/Areas/API/Controllers/DocumentTemplatePackageController.cs @@ -4,8 +4,11 @@ using Disco.Models.Services.Documents; using Disco.Services; using Disco.Services.Authorization; using Disco.Services.Documents; +using Disco.Services.Plugins; +using Disco.Services.Plugins.Features.DocumentHandlerProvider; using Disco.Services.Users; using Disco.Services.Web; +using Disco.Web.Areas.API.Models.DocumentTemplate; using System; using System.Collections.Generic; using System.IO; @@ -470,5 +473,65 @@ namespace Disco.Web.Areas.API.Controllers } #endregion + + #region Handlers + [HttpPost, ValidateAntiForgeryToken] + public virtual ActionResult GenerateDocumentHandlerUi(string id, string targetId, string handlerId) + { + DocumentTemplatePackageExtensions.GetPackageAndTarget(Database, Authorization, id, targetId, out var package, out var target, out var targetUser); + + var handlerManifest = Plugins.GetPluginFeature(handlerId, typeof(DocumentHandlerProviderFeature)); + + using (var handler = handlerManifest.CreateInstance()) + { + if (!handler.CanHandle(package, target)) + throw new NotSupportedException("Handler does not support this Document Template and Target"); + + var handlerPartialView = handler.GenerationOptionsUi; + + if (handlerPartialView == null) + throw new NotSupportedException("Handler does not have a Generation Options UI"); + + var model = handler.GetGenerationOptionsUiModel(package, target, targetUser, CurrentUser); + + return this.PrecompiledPartialView(handlerPartialView, model); + } + } + + [HttpPost, ValidateAntiForgeryToken] + public virtual ActionResult DocumentHandlers(string id, string targetId) + { + DocumentTemplatePackageExtensions.GetPackageAndTarget(Database, Authorization, id, targetId, out var package, out var target, out _); + + var handlers = Plugins.GetPluginFeatures(typeof(DocumentHandlerProviderFeature)) + .SelectMany(f => + { + using (var handler = f.CreateInstance()) + { + if (handler.CanHandle(package, target)) + return OneOf.Create(new DocumentHandlerModel() + { + Id = f.Id, + Title = handler.HandlerTitle, + Description = handler.HandlerDescription, + UiUrl = handler.GenerationOptionsUi == null ? null : Url.Action(MVC.API.DocumentTemplatePackage.GenerateDocumentHandlerUi(package.Id, target.AttachmentReferenceId, f.Id)), + Icon = handler.GenerationOptionsIcon, + }); + } + return Enumerable.Empty(); + }).ToList(); + + var model = new DocumentHandlersModel() + { + TemplateId = package.Id, + TemplateName = package.Description, + TargetId = target.AttachmentReferenceId, + TargetName = target.ToString(), + Handlers = handlers, + }; + + return Json(model); + } + #endregion } } \ No newline at end of file diff --git a/Disco.Web/ClientSource/Scripts/Modules/Disco-DocumentGenerator.js b/Disco.Web/ClientSource/Scripts/Modules/Disco-DocumentGenerator.js index d42a8e9d..1fb7b111 100644 --- a/Disco.Web/ClientSource/Scripts/Modules/Disco-DocumentGenerator.js +++ b/Disco.Web/ClientSource/Scripts/Modules/Disco-DocumentGenerator.js @@ -9,13 +9,14 @@ const generatePackageUrl = $container.attr('data-generatepackageurl'); const handlersPresent = $container.attr('data-handlerspresent') === 'true'; const handlersUrl = $container.attr('data-handlersurl'); + const handlersPackageUrl = $container.attr('data-handlerspackageurl'); let $handlersDialog = null; let lastTemplateId = null; const downloadPdf = function (templateId) { let action = generatePdfUrl; if (templateId.lastIndexOf('Package:', 0) === 0) { - templateId + templateId.substring(8); + templateId = templateId.substring(8); action = generatePackageUrl; } @@ -35,6 +36,12 @@ } const updateHandlers = function (templateId) { + let action = handlersUrl; + if (templateId.lastIndexOf('Package:', 0) === 0) { + templateId = templateId.substring(8); + action = handlersPackageUrl; + } + const $handlerPicker = $handlersDialog.find('.handlerPicker'); const $loadingUi = $handlersDialog.find('#Document_Generation_Dialog_Handlers_Loading'); @@ -43,9 +50,9 @@ var formData = new FormData(); formData.append('__RequestVerificationToken', document.body.dataset.antiforgery); - formData.append('templateId', decodeURI(templateId)); + formData.append('id', decodeURI(templateId)); formData.append('targetId', decodeURI(targetId)); - fetch(handlersUrl, { + fetch(action, { method: 'POST', body: formData }).then(r => r.json()) diff --git a/Disco.Web/ClientSource/Scripts/Modules/Disco-DocumentGenerator.min.js b/Disco.Web/ClientSource/Scripts/Modules/Disco-DocumentGenerator.min.js index 7000bed4..74984a7f 100644 --- a/Disco.Web/ClientSource/Scripts/Modules/Disco-DocumentGenerator.min.js +++ b/Disco.Web/ClientSource/Scripts/Modules/Disco-DocumentGenerator.min.js @@ -1 +1 @@ -(function(n,t,i){i(function(){let u=null;const r=i("#Document_Generation_Container"),f=r.find("#Document_Generate"),e=r.attr("data-targetid"),y=r.attr("data-targettype"),h=r.attr("data-generatepdfurl"),c=r.attr("data-generatepackageurl"),l=r.attr("data-handlerspresent")==="true",a=r.attr("data-handlersurl");let n=null,o=null;const s=function(n){let f=h;n.lastIndexOf("Package:",0)===0&&(n+n.substring(8),f=c);u||(u=i("