using Disco.BI.Extensions; using Disco.Models.Repository; using Disco.Models.Services.Documents; using Disco.Services; using Disco.Services.Authorization; using Disco.Services.Documents; using Disco.Services.Documents.ManagedGroups; using Disco.Services.Exporting; using Disco.Services.Interop.ActiveDirectory; using Disco.Services.Plugins; using Disco.Services.Plugins.Features.DocumentHandlerProvider; using Disco.Services.Tasks; using Disco.Services.Users; using Disco.Services.Web; using Disco.Web.Areas.API.Models.DocumentTemplate; using Disco.Web.Areas.Config.Models.DocumentTemplate; using Disco.Web.Extensions; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Data.Entity; using System.IO; using System.IO.Compression; using System.Linq; using System.Text.RegularExpressions; using System.Web; using System.Web.Mvc; using System.Web.UI.WebControls; namespace Disco.Web.Areas.API.Controllers { public partial class DocumentTemplateController : AuthorizedDatabaseController { private const string pDescription = "description"; private const string pScope = "scope"; private const string pFilterExpression = "filterexpression"; private const string pOnGenerateExpression = "ongenerateexpression"; private const string pOnImportAttachmentExpression = "onimportattachmentexpression"; private const string pFlattenForm = "flattenform"; private const string pIsHidden = "ishidden"; [DiscoAuthorize(Claims.Config.DocumentTemplate.Configure)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult Update(string id, string key, string value = null, bool redirect = false) { try { if (string.IsNullOrEmpty(id)) throw new ArgumentNullException("id"); if (string.IsNullOrEmpty(key)) throw new ArgumentNullException("key"); ScheduledTaskStatus resultTask = null; var documentTemplate = Database.DocumentTemplates.Find(id); if (documentTemplate != null) { switch (key.ToLower()) { case pDescription: UpdateDescription(documentTemplate, value); break; case pScope: resultTask = UpdateScope(documentTemplate, value); break; case pFilterExpression: Authorization.Require(Claims.Config.DocumentTemplate.ConfigureFilterExpression); UpdateFilterExpression(documentTemplate, value); break; case pOnGenerateExpression: UpdateOnGenerateExpression(documentTemplate, value); break; case pOnImportAttachmentExpression: UpdateOnImportAttachmentExpression(documentTemplate, value); break; case pFlattenForm: UpdateFlattenForm(documentTemplate, value); break; case pIsHidden: UpdateIsHidden(documentTemplate, value); break; default: throw new Exception("Invalid Update Key"); } } else { throw new Exception("Invalid Document Template Id"); } if (redirect) if (resultTask == null) { return RedirectToAction(MVC.Config.DocumentTemplate.Index(documentTemplate.Id)); } else { resultTask.SetFinishedUrl(Url.Action(MVC.Config.DocumentTemplate.Index(documentTemplate.Id))); return RedirectToAction(MVC.Config.Logging.TaskStatus(resultTask.SessionId)); } else return Ok(); } catch (Exception ex) { if (redirect) throw; else return BadRequest(ex.Message); } } [DiscoAuthorize(Claims.Config.DocumentTemplate.Upload)] [HttpGet] public virtual ActionResult Template(string id) { if (string.IsNullOrEmpty(id)) throw new ArgumentNullException("id"); var documentTemplate = Database.DocumentTemplates.Find(id); if (documentTemplate == null) throw new ArgumentException("Invalid Document Template Id", "id"); var filename = documentTemplate.RepositoryFilename(Database); if (System.IO.File.Exists(filename)) { return File(filename, DocumentTemplate.PdfMimeType, $"{documentTemplate.Id}.pdf"); } else { throw new InvalidOperationException("Template not found"); } } [DiscoAuthorizeAll(Claims.Config.DocumentTemplate.Upload, Claims.Config.DocumentTemplate.Configure)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult Template(string id, bool redirect, HttpPostedFileBase Template) { try { if (string.IsNullOrEmpty(id)) throw new ArgumentNullException("id"); var documentTemplate = Database.DocumentTemplates.Find(id); if (documentTemplate == null) throw new ArgumentException("Invalid Document Template Id", "id"); documentTemplate.SavePdfTemplate(Database, Template.InputStream); if (redirect) return RedirectToAction(MVC.Config.DocumentTemplate.Index(documentTemplate.Id)); else return Ok(); } catch (Exception ex) { if (redirect) throw; else return BadRequest(ex.Message); } } [DiscoAuthorize(Claims.Config.DocumentTemplate.Show)] [HttpGet] public virtual ActionResult TemplatePreview(string id) { if (string.IsNullOrEmpty(id)) throw new ArgumentNullException("id"); var documentTemplate = Database.DocumentTemplates.Find(id); if (documentTemplate == null) throw new ArgumentException("Invalid Document Template Id", "id"); var imageStream = new MemoryStream(); using (var previewImage = documentTemplate.GenerateTemplatePreview(Database, 450, 8, true)) { if (previewImage == null) { throw new InvalidOperationException("Template not found"); } previewImage.SavePng(imageStream); } imageStream.Position = 0; return File(imageStream, "image/png"); } #region Update Shortcut Methods [DiscoAuthorize(Claims.Config.DocumentTemplate.Configure)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult UpdateDescription(string id, string Description = null, bool redirect = false) { return Update(id, pDescription, Description, redirect); } [DiscoAuthorizeAll(Claims.Config.DocumentTemplate.Configure, Claims.Config.DocumentTemplate.ConfigureFilterExpression)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult UpdateFilterExpression(string id, string FilterExpression = null, bool redirect = false) { return Update(id, pFilterExpression, FilterExpression, redirect); } [DiscoAuthorizeAll(Claims.Config.DocumentTemplate.Configure, Claims.Config.DocumentTemplate.ConfigureFilterExpression)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult UpdateOnGenerateExpression(string id, string OnGenerateExpression = null, bool redirect = false) { return Update(id, pOnGenerateExpression, OnGenerateExpression, redirect); } [DiscoAuthorizeAll(Claims.Config.DocumentTemplate.Configure, Claims.Config.DocumentTemplate.ConfigureFilterExpression)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult UpdateOnImportAttachmentExpression(string id, string OnImportAttachmentExpression = null, bool redirect = false) { return Update(id, pOnImportAttachmentExpression, OnImportAttachmentExpression, redirect); } [DiscoAuthorize(Claims.Config.DocumentTemplate.Configure)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult UpdateFlattenForm(string id, string FlattenForm = null, bool redirect = false) { return Update(id, pFlattenForm, FlattenForm, redirect); } [DiscoAuthorize(Claims.Config.DocumentTemplate.Configure)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult UpdateIsHidden(string id, string IsHidden = null, bool redirect = false) { return Update(id, pIsHidden, IsHidden, redirect); } [DiscoAuthorize(Claims.Config.DocumentTemplate.Configure)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult UpdateScope(string id, string Scope = null, bool redirect = false) { return Update(id, pScope, Scope, redirect); } [DiscoAuthorize(Claims.Config.DocumentTemplate.Configure)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult UpdateJobSubTypes(string id, List JobSubTypes = null, bool redirect = false) { try { if (string.IsNullOrEmpty(id)) throw new ArgumentNullException("id"); var documentTemplate = Database.DocumentTemplates.Find(id); UpdateJobSubTypes(documentTemplate, JobSubTypes); if (redirect) return RedirectToAction(MVC.Config.DocumentTemplate.Index(documentTemplate.Id)); else return Ok(); } catch (Exception ex) { if (redirect) throw; else return BadRequest(ex.Message); } } [DiscoAuthorize(Claims.Config.DocumentTemplate.Configure)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult UpdateDevicesLinkedGroup(string id, string GroupId = null, DateTime? FilterBeginDate = null, bool redirect = false) { try { if (string.IsNullOrWhiteSpace(id)) throw new ArgumentNullException("id"); var documentTemplate = Database.DocumentTemplates.Find(id); if (documentTemplate == null) throw new ArgumentException("Invalid Document Template Id", "id"); var syncTaskStatus = UpdateDevicesLinkedGroup(documentTemplate, GroupId, FilterBeginDate); if (redirect) if (syncTaskStatus == null) return RedirectToAction(MVC.Config.DocumentTemplate.Index(documentTemplate.Id)); else { syncTaskStatus.SetFinishedUrl(Url.Action(MVC.Config.DocumentTemplate.Index(documentTemplate.Id))); return RedirectToAction(MVC.Config.Logging.TaskStatus(syncTaskStatus.SessionId)); } else return Ok(); } catch (Exception ex) { if (redirect) throw; else return BadRequest(ex.Message); } } [DiscoAuthorize(Claims.Config.DocumentTemplate.Configure)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult UpdateUsersLinkedGroup(string id, string GroupId = null, DateTime? FilterBeginDate = null, bool redirect = false) { try { if (string.IsNullOrWhiteSpace(id)) throw new ArgumentNullException("id"); var documentTemplate = Database.DocumentTemplates.Find(id); if (documentTemplate == null) throw new ArgumentException("Invalid Document Template Id", "id"); var syncTaskStatus = UpdateUsersLinkedGroup(documentTemplate, GroupId, FilterBeginDate); if (redirect) if (syncTaskStatus == null) return RedirectToAction(MVC.Config.DocumentTemplate.Index(documentTemplate.Id)); else { syncTaskStatus.SetFinishedUrl(Url.Action(MVC.Config.DocumentTemplate.Index(documentTemplate.Id))); return RedirectToAction(MVC.Config.Logging.TaskStatus(syncTaskStatus.SessionId)); } else return Ok(); } catch (Exception ex) { if (redirect) throw; else return BadRequest(ex.Message); } } #endregion #region Update Properties private void UpdateDescription(DocumentTemplate documentTemplate, string Description) { if (!string.IsNullOrWhiteSpace(Description)) { documentTemplate.Description = Description.Trim(); Database.SaveChanges(); return; } throw new Exception("Invalid Description"); } private ScheduledTaskStatus UpdateScope(DocumentTemplate documentTemplate, string Scope) { if (string.IsNullOrWhiteSpace(Scope) || !DocumentTemplate.DocumentTemplateScopes.ToList().Contains(Scope)) throw new ArgumentException("Invalid Scope", "Scope"); Database.Configuration.LazyLoadingEnabled = true; if (documentTemplate.Scope != Scope) { documentTemplate.Scope = Scope; if (documentTemplate.Scope != DocumentTemplate.DocumentTemplateScopes.Job && documentTemplate.JobSubTypes != null) { foreach (var st in documentTemplate.JobSubTypes.ToArray()) documentTemplate.JobSubTypes.Remove(st); } Database.SaveChanges(); // Trigger Managed Group Sync var managedGroups = new ADManagedGroup[] { DocumentTemplateDevicesManagedGroup.Initialize(documentTemplate), DocumentTemplateUsersManagedGroup.Initialize(documentTemplate) }; if (managedGroups.Any(mg => mg != null)) // Sync Group return ADManagedGroupsSyncTask.ScheduleSync(managedGroups.Where(mg => mg != null)); } return null; } private void UpdateFilterExpression(DocumentTemplate documentTemplate, string FilterExpression) { if (string.IsNullOrWhiteSpace(FilterExpression)) { documentTemplate.FilterExpression = null; } else { documentTemplate.FilterExpression = FilterExpression.Trim(); } // Invalidate Cache documentTemplate.FilterExpressionInvalidateCache(); Database.SaveChanges(); } private void UpdateOnGenerateExpression(DocumentTemplate documentTemplate, string OnGenerateExpression) { if (string.IsNullOrWhiteSpace(OnGenerateExpression)) { documentTemplate.OnGenerateExpression = null; } else { documentTemplate.OnGenerateExpression = OnGenerateExpression.Trim(); } // Invalidate Cache documentTemplate.OnGenerateExpressionInvalidateCache(); Database.SaveChanges(); } private void UpdateOnImportAttachmentExpression(DocumentTemplate documentTemplate, string OnImportAttachmentExpression) { if (string.IsNullOrWhiteSpace(OnImportAttachmentExpression)) { documentTemplate.OnImportAttachmentExpression = null; } else { documentTemplate.OnImportAttachmentExpression = OnImportAttachmentExpression.Trim(); } // Invalidate Cache documentTemplate.OnImportAttachmentExpressionInvalidateCache(); Database.SaveChanges(); } private void UpdateFlattenForm(DocumentTemplate documentTemplate, string FlattenForm) { if (string.IsNullOrWhiteSpace(FlattenForm)) { documentTemplate.FlattenForm = false; } else { if (bool.TryParse(FlattenForm, out var ff)) documentTemplate.FlattenForm = ff; else throw new Exception("Invalid Boolean Format"); } Database.SaveChanges(); } private void UpdateIsHidden(DocumentTemplate documentTemplate, string IsHidden) { if (string.IsNullOrWhiteSpace(IsHidden)) { documentTemplate.IsHidden = false; } else { if (bool.TryParse(IsHidden, out var value)) documentTemplate.IsHidden = value; else throw new Exception("Invalid Boolean Format"); } Database.SaveChanges(); } private void UpdateJobSubTypes(DocumentTemplate documentTemplate, List JobSubTypes) { Database.Configuration.LazyLoadingEnabled = true; // Remove All Existing if (documentTemplate.JobSubTypes != null) { foreach (var st in documentTemplate.JobSubTypes.ToArray()) documentTemplate.JobSubTypes.Remove(st); } // Add New if (JobSubTypes != null && JobSubTypes.Count > 0) { var subTypes = new List(); foreach (var stId in JobSubTypes) { var typeId = stId.Substring(0, stId.IndexOf("_")); var subTypeId = stId.Substring(stId.IndexOf("_") + 1); var subType = Database.JobSubTypes.FirstOrDefault(jst => jst.JobTypeId == typeId && jst.Id == subTypeId); subTypes.Add(subType); } documentTemplate.JobSubTypes = subTypes; } Database.SaveChanges(); } private ScheduledTaskStatus UpdateDevicesLinkedGroup(DocumentTemplate DocumentTemplate, string DevicesLinkedGroup, DateTime? FilterBeginDate) { var configJson = ADManagedGroup.ValidConfigurationToJson(DocumentTemplateDevicesManagedGroup.GetKey(DocumentTemplate), DevicesLinkedGroup, FilterBeginDate); if (DocumentTemplate.DevicesLinkedGroup != configJson) { DocumentTemplate.DevicesLinkedGroup = configJson; Database.SaveChanges(); var managedGroup = DocumentTemplateDevicesManagedGroup.Initialize(DocumentTemplate); if (managedGroup != null) // Sync Group return ADManagedGroupsSyncTask.ScheduleSync(managedGroup); } return null; } private ScheduledTaskStatus UpdateUsersLinkedGroup(DocumentTemplate DocumentTemplate, string UsersLinkedGroup, DateTime? FilterBeginDate) { var configJson = ADManagedGroup.ValidConfigurationToJson(DocumentTemplateUsersManagedGroup.GetKey(DocumentTemplate), UsersLinkedGroup, FilterBeginDate); if (DocumentTemplate.UsersLinkedGroup != configJson) { DocumentTemplate.UsersLinkedGroup = configJson; Database.SaveChanges(); var managedGroup = DocumentTemplateUsersManagedGroup.Initialize(DocumentTemplate); if (managedGroup != null) // Sync Group return ADManagedGroupsSyncTask.ScheduleSync(managedGroup); } return null; } #endregion #region Actions [DiscoAuthorize(Claims.Config.DocumentTemplate.UndetectedPages), OutputCache(NoStore = true, Duration = 0)] public virtual ActionResult ImporterThumbnail(Guid SessionId, int PageNumber) { var dataStoreSessionPagesCacheLocation = DataStore.CreateLocation(Database, "Cache\\DocumentDropBox_SessionPages"); var filename = Path.Combine(dataStoreSessionPagesCacheLocation, $"{SessionId}-{PageNumber}"); if (System.IO.File.Exists(filename)) return File(filename, "image/png"); else return File("~/ClientSource/Style/Images/Status/fileBroken256.png", "image/png"); } [DiscoAuthorize(Claims.Config.DocumentTemplate.UndetectedPages)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult ImporterUndetectedFiles() { var undetectedLocation = DataStore.CreateLocation(Database, "DocumentDropBox_Unassigned"); var undetectedDirectory = new DirectoryInfo(undetectedLocation); var m = undetectedDirectory.GetFiles("*.pdf").Select(f => new ImporterUndetectedFilesModel() { Id = Path.GetFileNameWithoutExtension(f.Name), Timestamp = f.CreationTime.ToFullDateTime(), TimestampUnixEpoc = f.CreationTime.ToUnixEpoc() }).ToArray(); return Json(m); } [DiscoAuthorize(Claims.Config.DocumentTemplate.UndetectedPages)] public virtual ActionResult ImporterUndetectedDataIdLookup(string id, string term, int limitCount = ActiveDirectory.DefaultSearchResultLimit) { if (!string.IsNullOrEmpty(id) && !string.IsNullOrWhiteSpace(term)) { string searchScope; if (id.StartsWith("--")) { switch (id.ToUpper()) { case "--DEVICE": searchScope = DocumentTemplate.DocumentTemplateScopes.Device; break; case "--JOB": searchScope = DocumentTemplate.DocumentTemplateScopes.Job; break; case "--USER": searchScope = DocumentTemplate.DocumentTemplateScopes.User; break; default: searchScope = null; break; } } else { var documentTemplate = Database.DocumentTemplates.Find(id); if (documentTemplate != null) searchScope = documentTemplate.Scope; else searchScope = null; } if (searchScope != null) { ImporterUndetectedDataIdLookupModel[] results; switch (searchScope) { case DocumentTemplate.DocumentTemplateScopes.Device: results = Disco.Services.Searching.Search.SearchDevices(Database, term, limitCount).Select(sr => ImporterUndetectedDataIdLookupModel.FromSearchResultItem(sr)).ToArray(); break; case DocumentTemplate.DocumentTemplateScopes.Job: results = Disco.Services.Searching.Search.SearchJobsTable(Database, term, limitCount, false).Items.Select(sr => ImporterUndetectedDataIdLookupModel.FromSearchResultItem(sr)).ToArray(); break; case DocumentTemplate.DocumentTemplateScopes.User: results = Disco.Services.Searching.Search.SearchUsers(Database, term, false, limitCount).Select(sr => ImporterUndetectedDataIdLookupModel.FromSearchResultItem(sr)).ToArray(); break; default: results = null; break; } if (results != null) return Json(results, JsonRequestBehavior.AllowGet); } } return Json(null, JsonRequestBehavior.AllowGet); } [DiscoAuthorize(Claims.Config.DocumentTemplate.UndetectedPages)] [HttpGet] public virtual ActionResult ImporterUndetectedFile(string id, bool? Source, bool? Thumbnail) { if (!Regex.IsMatch(id, @"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}_\d+$")) return BadRequest("Invalid page identifier"); var undetectedLocation = DataStore.CreateLocation(Database, "DocumentDropBox_Unassigned"); if (Source.HasValue && Source.Value) { var filename = Path.Combine(undetectedLocation, $"{id}.pdf"); if (System.IO.File.Exists(filename)) return File(filename, DocumentTemplate.PdfMimeType); else return HttpNotFound(); } else { if (Thumbnail.HasValue && Thumbnail.Value) { var filename = Path.Combine(undetectedLocation, $"{id}_thumbnail.png"); if (System.IO.File.Exists(filename)) return File(filename, "image/png"); else return File(Links.ClientSource.Style.Images.Status.fileBroken256_png, "image/png"); } else { var filename = Path.Combine(undetectedLocation, $"{id}.jpg"); if (System.IO.File.Exists(filename)) return File(filename, "image/jpeg"); else return File(Links.ClientSource.Style.Images.Status.fileBroken256_png, "image/png"); } } } [DiscoAuthorize(Claims.Config.DocumentTemplate.UndetectedPages)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult ImporterUndetectedAssign(string id, string DocumentTemplateId, string DataId) { if (!Regex.IsMatch(id, @"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}_\d+$")) return BadRequest("Invalid page identifier"); var undetectedLocation = DataStore.CreateLocation(Database, "DocumentDropBox_Unassigned"); var filename = Path.Combine(undetectedLocation, $"{id}.pdf"); var identifier = DocumentUniqueIdentifier.Create(Database, DocumentTemplateId, DataId, UserService.CurrentUser.UserId, DateTime.Now, 0); if (Disco.Services.Documents.AttachmentImport.Importer.ImportPdfAttachment(identifier, Database, filename) != null) { // Delete File System.IO.File.Delete(filename); // Delete Thumbnail/Preview var thumbnailFilename = Path.Combine(undetectedLocation, $"{id}_thumbnail.png"); if (System.IO.File.Exists(thumbnailFilename)) System.IO.File.Delete(thumbnailFilename); var previewFilename = Path.Combine(undetectedLocation, $"{id}.jpg"); if (System.IO.File.Exists(previewFilename)) System.IO.File.Delete(previewFilename); return Ok(); } else { return BadRequest("Unable to Import File with the supplied parameters"); } } [DiscoAuthorize(Claims.Config.DocumentTemplate.UndetectedPages)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult ImporterUndetectedDelete(string id) { if (!Regex.IsMatch(id, @"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}_\d+$")) return BadRequest("Invalid page identifier"); var undetectedLocation = DataStore.CreateLocation(Database, "DocumentDropBox_Unassigned"); var filename = Path.Combine(undetectedLocation, $"{id}.pdf"); if (System.IO.File.Exists(filename)) { // Delete File System.IO.File.Delete(filename); // Delete Thumbnail/Preview var thumbnailFilename = Path.Combine(undetectedLocation, $"{id}_thumbnail.png"); if (System.IO.File.Exists(thumbnailFilename)) System.IO.File.Delete(thumbnailFilename); var previewFilename = Path.Combine(undetectedLocation, $"{id}.jpg"); if (System.IO.File.Exists(previewFilename)) System.IO.File.Delete(previewFilename); return Ok(); } else { return BadRequest("File Not Found"); } } [DiscoAuthorizeAll(Claims.Config.DeviceModel.Show, Claims.Config.DocumentTemplate.BulkGenerate)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult BulkGenerateDeviceModel(string id, int deviceGroupId) { var template = Database.DocumentTemplates.FirstOrDefault(t => t.Id == id); if (template == null) return HttpNotFound("Document Template not found"); var deviceModel = Database.DeviceModels.FirstOrDefault(m => m.Id == deviceGroupId); if (deviceModel is null) return HttpNotFound("Device Model not found"); List dataIds; switch (template.AttachmentType) { case AttachmentTypes.Device: dataIds = Database.Devices.Where(d => d.DeviceModelId == deviceModel.Id && d.DecommissionedDate == null).Select(d => d.SerialNumber).ToList(); break; case AttachmentTypes.Job: dataIds = Database.Jobs.Where(j => j.ClosedDate == null && j.Device.DeviceModelId == deviceModel.Id).Select(j => j.Id).AsEnumerable().Select(j => j.ToString()).ToList(); break; case AttachmentTypes.User: dataIds = Database.Users.Where(u => u.DeviceUserAssignments.Any(a => a.UnassignedDate == null && a.Device.DeviceModelId == deviceModel.Id)).Select(u => u.UserId).ToList(); break; default: throw new NotSupportedException("The template type is not supported"); } if (!dataIds.Any()) return HttpNotFound($"No {template.AttachmentType} targets in scope"); return BulkGenerate(template.Id, string.Join(Environment.NewLine, dataIds)); } [DiscoAuthorizeAll(Claims.Config.DeviceProfile.Show, Claims.Config.DocumentTemplate.BulkGenerate)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult BulkGenerateDeviceProfile(string id, int deviceGroupId) { var template = Database.DocumentTemplates.FirstOrDefault(t => t.Id == id); if (template == null) return HttpNotFound("Document Template not found"); var deviceProfile = Database.DeviceProfiles.FirstOrDefault(m => m.Id == deviceGroupId); if (deviceProfile is null) return HttpNotFound("Device Profile not found"); List dataIds; switch (template.AttachmentType) { case AttachmentTypes.Device: dataIds = Database.Devices.Where(d => d.DeviceProfileId == deviceProfile.Id && d.DecommissionedDate == null).Select(d => d.SerialNumber).ToList(); break; case AttachmentTypes.Job: dataIds = Database.Jobs.Where(j => j.ClosedDate == null && j.Device.DeviceProfileId == deviceProfile.Id).Select(j => j.Id).AsEnumerable().Select(j => j.ToString()).ToList(); break; case AttachmentTypes.User: dataIds = Database.Users.Where(u => u.DeviceUserAssignments.Any(a => a.UnassignedDate == null && a.Device.DeviceProfileId == deviceProfile.Id)).Select(u => u.UserId).ToList(); break; default: throw new NotSupportedException("The template type is not supported"); } if (!dataIds.Any()) return HttpNotFound($"No {template.AttachmentType} targets in scope"); return BulkGenerate(template.Id, string.Join(Environment.NewLine, dataIds)); } [DiscoAuthorizeAll(Claims.Config.DeviceBatch.Show, Claims.Config.DocumentTemplate.BulkGenerate)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult BulkGenerateDeviceBatch(string id, int deviceGroupId) { var template = Database.DocumentTemplates.FirstOrDefault(t => t.Id == id); if (template == null) return HttpNotFound("Document Template not found"); var deviceBatch = Database.DeviceBatches.FirstOrDefault(m => m.Id == deviceGroupId); if (deviceBatch is null) return HttpNotFound("Device Batch not found"); List dataIds; switch (template.AttachmentType) { case AttachmentTypes.Device: dataIds = Database.Devices.Where(d => d.DeviceBatchId == deviceBatch.Id && d.DecommissionedDate == null).Select(d => d.SerialNumber).ToList(); break; case AttachmentTypes.Job: dataIds = Database.Jobs.Where(j => j.ClosedDate == null && j.Device.DeviceBatchId == deviceBatch.Id).Select(j => j.Id).AsEnumerable().Select(j => j.ToString()).ToList(); break; case AttachmentTypes.User: dataIds = Database.Users.Where(u => u.DeviceUserAssignments.Any(a => a.UnassignedDate == null && a.Device.DeviceBatchId == deviceBatch.Id)).Select(u => u.UserId).ToList(); break; default: throw new NotSupportedException("The template type is not supported"); } if (!dataIds.Any()) return HttpNotFound($"No {template.AttachmentType} targets in scope"); return BulkGenerate(template.Id, string.Join(Environment.NewLine, dataIds)); } [DiscoAuthorize(Claims.Config.DocumentTemplate.BulkGenerate)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult BulkGenerate(string id, string dataIds = null, bool insertBlankPage = false) { if (string.IsNullOrEmpty(id)) throw new ArgumentNullException("id"); if (string.IsNullOrEmpty(dataIds)) throw new ArgumentNullException("DataIds"); var documentTemplate = Database.DocumentTemplates.Find(id); if (documentTemplate == null) throw new ArgumentException("Invalid Document Template Id", "id"); switch (documentTemplate.Scope) { case DocumentTemplate.DocumentTemplateScopes.Device: Authorization.Require(Claims.Device.Actions.GenerateDocuments); break; case DocumentTemplate.DocumentTemplateScopes.Job: Authorization.Require(Claims.Job.Actions.GenerateDocuments); break; case DocumentTemplate.DocumentTemplateScopes.User: Authorization.Require(Claims.User.Actions.GenerateDocuments); break; default: throw new InvalidOperationException("Unknown DocumentType Scope"); } var ids = dataIds.Split(new string[] { Environment.NewLine, ",", ";" }, StringSplitOptions.RemoveEmptyEntries).Select(d => d.Trim()).Where(d => !string.IsNullOrEmpty(d)).ToList(); var timeStamp = DateTime.Now; var taskStatus = DocumentBulkGenerateTask.ScheduleNow(BI.Interop.Pdf.PdfGenerator.GenerateBulkFromTemplate, documentTemplate, UserService.CurrentUser, timeStamp, insertBlankPage, ids); var fileName = $"{documentTemplate.Id}_Bulk_{timeStamp:yyyyMMdd-HHmmss}.pdf"; taskStatus.SetFinishedUrl(Url.Action(MVC.Config.DocumentTemplate.Index(documentTemplate.Id, Guid.Parse(taskStatus.SessionId), fileName))); if (!taskStatus.WaitUntilFinished(TimeSpan.FromSeconds(1))) return RedirectToAction(MVC.Config.Logging.TaskStatus(taskStatus.SessionId)); var stream = DocumentBulkGenerateTask.GetCached(Database, Guid.Parse(taskStatus.SessionId)); return File(stream, "application/pdf", fileName); } [DiscoAuthorize(Claims.Config.DocumentTemplate.BulkGenerate)] public virtual ActionResult BulkGenerateDownload(Guid id, string fileName) { var stream = DocumentBulkGenerateTask.GetCached(Database, id); return File(stream, "application/pdf", fileName); } private List BulkGenerateEvaluateScope(string scope, T arg, Func> userScope, Func> deviceScope) { if (string.IsNullOrWhiteSpace(scope)) throw new ArgumentNullException(nameof(scope), "Scope is required."); if ("user".Equals(scope, StringComparison.OrdinalIgnoreCase)) return userScope(arg); else if ("device".Equals(scope, StringComparison.OrdinalIgnoreCase)) return deviceScope(arg); else throw new ArgumentException("Invalid scope specified. Use 'user' or 'device'.", nameof(scope)); } [DiscoAuthorizeAll(Claims.Config.DocumentTemplate.BulkGenerate)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult BulkGenerateAddUsers(string scope, string userIds) { if (string.IsNullOrWhiteSpace(scope) || string.IsNullOrWhiteSpace(userIds)) return BadRequest(); var dataIds = userIds.Split(new string[] { Environment.NewLine, ",", ";" }, StringSplitOptions.RemoveEmptyEntries).Select(d => d.Trim()).Where(d => !string.IsNullOrEmpty(d)).ToList(); if (dataIds.Count == 0) return Json(new List()); var results = BulkGenerateEvaluateScope(scope, dataIds, BulkGenerateUserAddUsers, BulkGenerateDeviceAddUsers); return Json(results); } private List BulkGenerateUserAddUsers(List dataIds) { var results = new List(dataIds.Count); foreach (var dataId in dataIds) { var accountId = ActiveDirectory.ParseDomainAccountId(dataId); if (UserService.TryGetUser(accountId, Database, true, out var user)) { results.Add(new BulkGenerateItemModel() { Id = user.UserId, UserId = user.UserId, UserDisplayName = user.DisplayName, UserEmailAddress = user.EmailAddress, Scope = $"Matched '{dataId}'", IsError = false, }); continue; } else { var adObject = ActiveDirectory.RetrieveADObject(accountId, true); if (adObject == null) { results.Add(new BulkGenerateItemModel() { Id = dataId, UserDisplayName = dataId, Scope = $"Unknown User or Security Group '{dataId}'", IsError = true, }); continue; } if (adObject is ADGroup group) { foreach (var adUser in group.GetUserMembersRecursive()) { results.Add(new BulkGenerateItemModel() { Id = adUser.Id, UserId = adUser.Id, UserDisplayName = adUser.DisplayName, UserEmailAddress = adUser.Email, Scope = $"Group Member '{group.Name}'", IsError = false, }); } continue; } else { results.Add(new BulkGenerateItemModel() { Id = dataId, UserDisplayName = dataId, Scope = $"Unexpected AD Object found at '{adObject.DistinguishedName}'", IsError = true, }); continue; } } } return results; } private List BulkGenerateDeviceAddUsers(List dataIds) { var results = new List(dataIds.Count); foreach (var dataId in dataIds) { var accountId = ActiveDirectory.ParseDomainAccountId(dataId); var dbUser = Database.Users .Include(u => u.DeviceUserAssignments) .Where(u => u.UserId == accountId) .FirstOrDefault(); if (dbUser != null) { var assignments = dbUser.CurrentDeviceAssignments; if (assignments.Any()) { if (UserService.TryGetUser(accountId, Database, true, out var adUser)) dbUser = adUser; // use updated user from AD foreach (var assignment in assignments) { results.Add(new BulkGenerateItemModel() { Id = assignment.DeviceSerialNumber, UserId = dbUser.UserId, UserDisplayName = dbUser.DisplayName, UserEmailAddress = dbUser.EmailAddress, Scope = $"Matched assigned user '{dataId}'", IsError = false, }); } } } } return results; } [DiscoAuthorizeAll(Claims.Config.DocumentTemplate.BulkGenerate, Claims.Device.Actions.GenerateDocuments)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult BulkGenerateAddDevices(string deviceSerialNumbers) { if (string.IsNullOrWhiteSpace(deviceSerialNumbers)) return BadRequest(); var dataIds = deviceSerialNumbers.Split(new string[] { Environment.NewLine, ",", ";" }, StringSplitOptions.RemoveEmptyEntries).Select(d => d.Trim()).Where(d => !string.IsNullOrEmpty(d)).ToList(); if (dataIds.Count == 0) return Json(new List()); var results = new List(); var devices = Database.Devices.Include(d => d.AssignedUser).Where(d => dataIds.Contains(d.SerialNumber)).ToDictionary(d => d.SerialNumber, StringComparer.OrdinalIgnoreCase); foreach (var deviceSerialNumber in dataIds) { if (devices.TryGetValue(deviceSerialNumber, out var device)) { var user = device.AssignedUser; if (user != null && UserService.TryGetUser(user.UserId, Database, true, out var adUser)) user = adUser; // use updated user from AD results.Add(new BulkGenerateItemModel() { Id = device.SerialNumber, UserId = user?.UserId, UserDisplayName = user?.DisplayName, UserEmailAddress = user?.EmailAddress, Scope = $"Matched '{deviceSerialNumber}'", IsError = false, }); } else { results.Add(new BulkGenerateItemModel() { Id = deviceSerialNumber, UserDisplayName = deviceSerialNumber, Scope = $"Unknown Device '{deviceSerialNumber}'", IsError = true, }); } } return Json(results); } [DiscoAuthorizeAll(Claims.Config.DocumentTemplate.BulkGenerate, Claims.User.Actions.GenerateDocuments)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult BulkGenerateAddGroupMembers(string groupId) { if (string.IsNullOrWhiteSpace(groupId)) return BadRequest(); var results = new List(); var accountId = ActiveDirectory.ParseDomainAccountId(groupId); var adObject = ActiveDirectory.RetrieveADObject(accountId, true); if (adObject == null) { results.Add(new BulkGenerateItemModel() { Id = groupId, UserDisplayName = groupId, Scope = $"Unknown Security Group '{groupId}'", IsError = true, }); } else if (adObject is ADGroup group) { foreach (var adUser in group.GetUserMembersRecursive()) { results.Add(new BulkGenerateItemModel() { Id = adUser.Id, UserId = adUser.Id, UserDisplayName = adUser.DisplayName, UserEmailAddress = adUser.Email, Scope = $"Group Member '{group.Name}'", IsError = false, }); } } else if (adObject is ADUserAccount user) { results.Add(new BulkGenerateItemModel() { Id = user.Id, UserId = user.Id, UserDisplayName = user.DisplayName, UserEmailAddress = user.Email, Scope = $"Matched '{groupId}'", IsError = false, }); } else { results.Add(new BulkGenerateItemModel() { Id = groupId, UserDisplayName = groupId, Scope = $"Unexpected AD Object found at '{adObject.DistinguishedName}'", IsError = true, }); } return Json(results); } [DiscoAuthorizeAll(Claims.Config.DocumentTemplate.BulkGenerate, Claims.User.Actions.GenerateDocuments)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult BulkGenerateAddUserFlag(int flagId) { if (flagId <= 0) return BadRequest(); var results = new List(); var flag = Database.UserFlags.Include(f => f.UserFlagAssignments.Select(a => a.User)).FirstOrDefault(f => f.Id == flagId); if (flag == null) { results.Add(new BulkGenerateItemModel() { Id = flagId.ToString(), UserDisplayName = flagId.ToString(), Scope = $"Unknown User Flag '{flagId}'", IsError = true, }); } else { var assignments = flag.UserFlagAssignments.Where(a => a.RemovedDate == null).ToList(); if (assignments.Count == 0) { results.Add(new BulkGenerateItemModel() { Id = flag.Name, UserDisplayName = flag.Name, Scope = $"User Flag has no active assignments", IsError = true, }); } else { foreach (var assignment in assignments) { var user = assignment.User; if (UserService.TryGetUser(user.UserId, Database, true, out var adUser)) user = adUser; // use updated user from AD results.Add(new BulkGenerateItemModel() { Id = assignment.UserId, UserId = user?.UserId, UserDisplayName = user?.DisplayName, UserEmailAddress = user?.EmailAddress, Scope = $"Assigned User Flag '{flag.Name}'", IsError = false, }); } } } return Json(results); } [DiscoAuthorizeAll(Claims.Config.DocumentTemplate.BulkGenerate)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult BulkGenerateAddDeviceFlag(int flagId) { if (flagId <= 0) return BadRequest(); var results = new List(); var flag = Database.DeviceFlags.Include(f => f.DeviceFlagAssignments.Select(a => a.Device.AssignedUser)).FirstOrDefault(f => f.Id == flagId); if (flag == null) { results.Add(new BulkGenerateItemModel() { Id = flagId.ToString(), UserDisplayName = flagId.ToString(), Scope = $"Unknown User Flag '{flagId}'", IsError = true, }); } else { var assignments = flag.DeviceFlagAssignments.Where(a => a.RemovedDate == null).ToList(); if (assignments.Count == 0) { results.Add(new BulkGenerateItemModel() { Id = flag.Name, UserDisplayName = flag.Name, Scope = $"Device Flag has no active assignments", IsError = true, }); } else { foreach (var assignment in assignments) { var user = assignment.Device.AssignedUser; if (user != null && UserService.TryGetUser(user.UserId, Database, true, out var adUser)) user = adUser; // use updated user from AD results.Add(new BulkGenerateItemModel() { Id = assignment.DeviceSerialNumber, UserId = user?.UserId, UserDisplayName = user?.DisplayName, UserEmailAddress = user?.EmailAddress, Scope = $"Assigned User Flag '{flag.Name}'", IsError = false, }); } } } return Json(results); } [DiscoAuthorizeAll(Claims.Config.DocumentTemplate.BulkGenerate)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult BulkGenerateAddDeviceProfile(string scope, int deviceProfileId) { if (string.IsNullOrWhiteSpace(scope) && deviceProfileId <= 0) return BadRequest(); var profile = Database.DeviceProfiles.Include(p => p.Devices.Select(a => a.AssignedUser)).FirstOrDefault(f => f.Id == deviceProfileId); if (profile == null) { var result = new List() { new BulkGenerateItemModel() { Id = deviceProfileId.ToString(), UserDisplayName = deviceProfileId.ToString(), Scope = $"Unknown Device Profile '{deviceProfileId}'", IsError = true, } }; return Json(result); } var results = BulkGenerateEvaluateScope(scope, profile, BulkGenerateUserAddDeviceProfile, BulkGenerateDeviceAddDeviceProfile); return Json(results); } private List BulkGenerateUserAddDeviceProfile(DeviceProfile profile) { var results = new List(); var assignments = profile.Devices.Where(d => d.AssignedUser != null).ToList(); if (assignments.Count == 0) { results.Add(new BulkGenerateItemModel() { Id = profile.Name, UserDisplayName = profile.Name, Scope = $"Device Profile has no devices with active assignments", IsError = true, }); } else { foreach (var assignment in assignments) { var user = assignment.AssignedUser; if (UserService.TryGetUser(user.UserId, Database, true, out var adUser)) user = adUser; // use updated user from AD results.Add(new BulkGenerateItemModel() { Id = user.UserId, UserId = user.UserId, UserDisplayName = user.DisplayName, UserEmailAddress = user.EmailAddress, Scope = $"Device Profile '{profile.Name}' Matches Assigned Device '{assignment.SerialNumber}'", IsError = false, }); } } return results; } private List BulkGenerateDeviceAddDeviceProfile(DeviceProfile profile) { var results = new List(); if (profile.Devices.Count == 0) { results.Add(new BulkGenerateItemModel() { Id = profile.Name, UserDisplayName = profile.Name, Scope = $"Device Profile has no devices", IsError = true, }); } else { foreach (var device in profile.Devices) { var user = device.AssignedUser; if (user != null && UserService.TryGetUser(user.UserId, Database, true, out var adUser)) user = adUser; // use updated user from AD results.Add(new BulkGenerateItemModel() { Id = device.SerialNumber, UserId = user?.UserId, UserDisplayName = user?.DisplayName, UserEmailAddress = user?.EmailAddress, Scope = $"Device Profile '{profile.Name}'", IsError = false, }); } } return results; } [DiscoAuthorizeAll(Claims.Config.DocumentTemplate.BulkGenerate)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult BulkGenerateAddDeviceBatch(string scope, int deviceBatchId) { if (deviceBatchId <= 0) return BadRequest(); var batch = Database.DeviceBatches.Include(p => p.Devices.Select(a => a.AssignedUser)).FirstOrDefault(f => f.Id == deviceBatchId); if (batch == null) { var result = new List() { new BulkGenerateItemModel() { Id = deviceBatchId.ToString(), UserDisplayName = deviceBatchId.ToString(), Scope = $"Unknown Device Batch '{deviceBatchId}'", IsError = true, } }; return Json(result); } var results = BulkGenerateEvaluateScope(scope, batch, BulkGenerateUserAddDeviceBatch, BulkGenerateDeviceAddDeviceBatch); return Json(results); } private List BulkGenerateUserAddDeviceBatch(DeviceBatch batch) { var assignments = batch.Devices.Where(d => d.AssignedUser != null).ToList(); var results = new List(); if (assignments.Count == 0) { results.Add(new BulkGenerateItemModel() { Id = batch.Name, UserDisplayName = batch.Name, Scope = $"Device Batch has no devices with active assignments", IsError = true, }); } else { foreach (var assignment in assignments) { var user = assignment.AssignedUser; if (UserService.TryGetUser(user.UserId, Database, true, out var adUser)) user = adUser; // use updated user from AD results.Add(new BulkGenerateItemModel() { Id = assignment.AssignedUserId, UserId = user?.UserId, UserDisplayName = user.DisplayName, UserEmailAddress = user.EmailAddress, Scope = $"Device Batch '{batch.Name}' Matches Assigned Device '{assignment.SerialNumber}'", IsError = false, }); } } return results; } private List BulkGenerateDeviceAddDeviceBatch(DeviceBatch batch) { var results = new List(); if (batch.Devices.Count == 0) { results.Add(new BulkGenerateItemModel() { Id = batch.Name, UserDisplayName = batch.Name, Scope = $"Device Batch has no devices", IsError = true, }); } else { foreach (var device in batch.Devices) { var user = device.AssignedUser; if (user != null && UserService.TryGetUser(user.UserId, Database, true, out var adUser)) user = adUser; // use updated user from AD results.Add(new BulkGenerateItemModel() { Id = device.SerialNumber, UserId = user?.UserId, UserDisplayName = user?.DisplayName, UserEmailAddress = user?.EmailAddress, Scope = $"Device Batch '{batch.Name}'", IsError = false, }); } } return results; } [DiscoAuthorizeAll(Claims.Config.DocumentTemplate.BulkGenerate)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult BulkGenerateAddDocumentAttachment(string scope, string documentTemplateId, DateTime? threshold) { if (string.IsNullOrWhiteSpace(documentTemplateId)) return BadRequest(); var template = Database.DocumentTemplates.FirstOrDefault(f => f.Id == documentTemplateId); if (template == null) { var result = new List() { new BulkGenerateItemModel() { Id = documentTemplateId, UserDisplayName = documentTemplateId, Scope = $"Unknown Document Template '{documentTemplateId}'", IsError = true, } }; return Json(result); } var results = BulkGenerateEvaluateScope(scope, (template, threshold), BulkGenerateUserAddDocumentTemplateAttachment, BulkGenerateDeviceAddDocumentTemplateAttachment); return Json(results); } private List BulkGenerateUserAddDocumentTemplateAttachment((DocumentTemplate template, DateTime? threshold) arg) { var (template, threshold) = arg; var results = new List(); switch (template.AttachmentType) { case AttachmentTypes.Device: var deviceAssignments = Database.DeviceAttachments .Include(a => a.Device.AssignedUser) .Where(a => a.DocumentTemplateId == template.Id && (threshold == null || a.Timestamp > threshold) && a.Device.AssignedUserId != null) .ToList(); foreach (var assignment in deviceAssignments) { var user = assignment.Device.AssignedUser; if (UserService.TryGetUser(user.UserId, Database, true, out var adUser)) user = adUser; // use updated user from AD results.Add(new BulkGenerateItemModel() { Id = assignment.Device.AssignedUserId, UserId = user.UserId, UserDisplayName = user.DisplayName, UserEmailAddress = user.EmailAddress, Scope = $"Document Template '{template.Id}' Attachment Matches Assigned Device '{assignment.Device.SerialNumber}'", IsError = false, }); } break; case AttachmentTypes.Job: var jobAssignments = Database.JobAttachments .Include(a => a.Job.User) .Where(a => a.DocumentTemplateId == template.Id && (threshold == null || a.Timestamp > threshold) && a.Job.UserId != null) .ToList(); foreach (var assignment in jobAssignments) { var user = assignment.Job.User; if (UserService.TryGetUser(user.UserId, Database, true, out var adUser)) user = adUser; // use updated user from AD results.Add(new BulkGenerateItemModel() { Id = assignment.Job.UserId, UserId = user.UserId, UserDisplayName = user.DisplayName, UserEmailAddress = user.EmailAddress, Scope = $"Document Template '{template.Id}' Attachment Matches Job '{assignment.Job.Id}'", IsError = false, }); } break; case AttachmentTypes.User: var userAssignments = Database.UserAttachments .Include(a => a.User) .Where(a => a.DocumentTemplateId == template.Id && (threshold == null || a.Timestamp > threshold) && a.UserId != null) .ToList(); foreach (var assignment in userAssignments) { var user = assignment.User; if (UserService.TryGetUser(user.UserId, Database, true, out var adUser)) user = adUser; // use updated user from AD results.Add(new BulkGenerateItemModel() { Id = assignment.UserId, UserId = user.UserId, UserDisplayName = user.DisplayName, UserEmailAddress = user.EmailAddress, Scope = $"Document Template '{template.Id}' Attachment Matches User", IsError = false, }); } break; default: throw new NotSupportedException(); } if (results.Count == 0) { if (threshold.HasValue) { results.Add(new BulkGenerateItemModel() { Id = template.Id, UserDisplayName = template.Id, Scope = $"Document Template has no attachments with associated users after {threshold:yyyy-MM-dd}", IsError = true, }); } else { results.Add(new BulkGenerateItemModel() { Id = template.Id, UserDisplayName = template.Id, Scope = $"Document Template has no attachments with associated users", IsError = true, }); } } else { var distinctSet = new HashSet(StringComparer.Ordinal); results = results.Where(r => distinctSet.Add(r.Id)).ToList(); } return results; } private List BulkGenerateDeviceAddDocumentTemplateAttachment((DocumentTemplate template, DateTime? threshold) arg) { var (template, threshold) = arg; var results = new List(); switch (template.AttachmentType) { case AttachmentTypes.Device: var deviceAssignments = Database.DeviceAttachments .Include(a => a.Device.AssignedUser) .Where(a => a.DocumentTemplateId == template.Id && (threshold == null || a.Timestamp > threshold)) .ToList(); foreach (var assignment in deviceAssignments) { var user = assignment.Device.AssignedUser; if (user != null && UserService.TryGetUser(user.UserId, Database, true, out var adUser)) user = adUser; // use updated user from AD results.Add(new BulkGenerateItemModel() { Id = assignment.DeviceSerialNumber, UserId = user?.UserId, UserDisplayName = user?.DisplayName, UserEmailAddress = user?.EmailAddress, Scope = $"Document Template '{template.Id}'", IsError = false, }); } break; case AttachmentTypes.Job: var jobAssignments = Database.JobAttachments .Include(a => a.Job.User) .Where(a => a.DocumentTemplateId == template.Id && (threshold == null || a.Timestamp > threshold) && a.Job.DeviceSerialNumber != null) .ToList(); foreach (var assignment in jobAssignments) { var user = assignment.Job.User; if (user != null && UserService.TryGetUser(user.UserId, Database, true, out var adUser)) user = adUser; // use updated user from AD results.Add(new BulkGenerateItemModel() { Id = assignment.Job.DeviceSerialNumber, UserId = user?.UserId, UserDisplayName = user?.DisplayName, UserEmailAddress = user?.EmailAddress, Scope = $"Document Template '{template.Id}' Attachment Matches Job '{assignment.Job.Id}'", IsError = false, }); } break; case AttachmentTypes.User: var userAssignments = Database.UserAttachments .Include(a => a.User.DeviceUserAssignments) .Where(a => a.DocumentTemplateId == template.Id && (threshold == null || a.Timestamp > threshold)) .ToList(); foreach (var assignment in userAssignments) { var userDeviceAssignments = assignment.User.CurrentDeviceAssignments; if (userDeviceAssignments.Any()) { var user = assignment.User; if (UserService.TryGetUser(user.UserId, Database, true, out var adUser)) user = adUser; // use updated user from AD foreach (var userDeviceAssignment in userDeviceAssignments) { results.Add(new BulkGenerateItemModel() { Id = userDeviceAssignment.DeviceSerialNumber, UserId = user.UserId, UserDisplayName = user.DisplayName, UserEmailAddress = user.EmailAddress, Scope = $"Document Template '{template.Id}' Attachment Matches User '{user.UserId}'", IsError = false, }); } } } break; default: throw new NotSupportedException(); } if (results.Count == 0) { if (threshold.HasValue) { results.Add(new BulkGenerateItemModel() { Id = template.Id, UserDisplayName = template.Id, Scope = $"Document Template has no attachments after {threshold:yyyy-MM-dd}", IsError = true, }); } else { results.Add(new BulkGenerateItemModel() { Id = template.Id, UserDisplayName = template.Id, Scope = $"Document Template has no attachments", IsError = true, }); } } else { var distinctSet = new HashSet(StringComparer.Ordinal); results = results.Where(r => distinctSet.Add(r.Id)).ToList(); } return results; } [DiscoAuthorizeAll(Claims.Config.DocumentTemplate.BulkGenerate)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult BulkGenerateGetUserDetailValues(string key) { if (string.IsNullOrWhiteSpace(key)) return BadRequest(); var results = Database.UserDetails.Where(d => d.Scope == "Details" && d.Key == key).Select(d => d.Value).Distinct().ToList(); return Json(results); } [DiscoAuthorizeAll(Claims.Config.DocumentTemplate.BulkGenerate)] [HttpPost, ValidateAntiForgeryToken, ValidateInput(false)] public virtual ActionResult BulkGenerateAddUserDetail(string scope, string key, string value) { if (string.IsNullOrWhiteSpace(key)) return BadRequest(); var results = BulkGenerateEvaluateScope(scope, (key, value), BulkGenerateUserAddUserDetail, BulkGenerateDeviceAddUserDetail); return Json(results); } private List BulkGenerateUserAddUserDetail((string key, string value) arg) { var (key, value) = arg; var results = new List(); var query = Database.UserDetails.Include(d => d.User).Where(d => d.Scope == "Details" && d.Key == key); if (!string.IsNullOrWhiteSpace(value)) { query = query.Where(d => d.Value == value); } var details = query.ToList(); if (details.Count == 0) { results.Add(new BulkGenerateItemModel() { Id = key, UserDisplayName = $"{key}{(string.IsNullOrWhiteSpace(value) ? null : $":{value}")}", Scope = $"User Detail '{key}' didn't match any users{(string.IsNullOrWhiteSpace(value) ? null : $" with the value '{value}'")}", IsError = true, }); } else { foreach (var dbUser in details.Select(d => d.User).Distinct()) { var user = dbUser; if (UserService.TryGetUser(user.UserId, Database, true, out var adUser)) user = adUser; // use updated user from AD results.Add(new BulkGenerateItemModel() { Id = user.UserId, UserId = user.UserId, UserDisplayName = user.DisplayName, UserEmailAddress = user.EmailAddress, Scope = $"User Detail '{key}'{(string.IsNullOrWhiteSpace(value) ? null : $" with the value '{value}'")} Matches User", IsError = false, }); } } return results; } private List BulkGenerateDeviceAddUserDetail((string key, string value) arg) { var (key, value) = arg; var results = new List(); var query = Database.UserDetails.Include(d => d.User.DeviceUserAssignments).Where(d => d.Scope == "Details" && d.Key == key); if (!string.IsNullOrWhiteSpace(value)) { query = query.Where(d => d.Value == value); } var details = query.ToList(); if (details.Count > 0) { foreach (var dbUser in details.Select(d => d.User).Distinct()) { var assignments = dbUser.CurrentDeviceAssignments; if (assignments.Count == 0) continue; var user = dbUser; if (UserService.TryGetUser(user.UserId, Database, true, out var adUser)) user = adUser; // use updated user from AD foreach (var assignment in assignments) { results.Add(new BulkGenerateItemModel() { Id = assignment.DeviceSerialNumber, UserId = user.UserId, UserDisplayName = user.DisplayName, UserEmailAddress = user.EmailAddress, Scope = $"User Detail '{key}'{(string.IsNullOrWhiteSpace(value) ? null : $" with the value '{value}'")} Matches User '{user.UserId}'", IsError = false, }); } } } if (results.Count == 0) { results.Add(new BulkGenerateItemModel() { Id = key, UserDisplayName = $"{key}{(string.IsNullOrWhiteSpace(value) ? null : $":{value}")}", Scope = $"User Detail '{key}' didn't match any devices{(string.IsNullOrWhiteSpace(value) ? null : $" with the value '{value}'")}", IsError = true, }); } else { var distinctSet = new HashSet(StringComparer.Ordinal); results = results.Where(r => distinctSet.Add(r.Id)).ToList(); } return results; } [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult Generate(string id, string targetId) { Disco.Services.DocumentTemplateExtensions.GetTemplateAndTarget(Database, Authorization, id, targetId, out var template, out var target, out _); // generate document var timestamp = DateTime.Now; var document = default(Stream); using (var state = DocumentState.DefaultState()) { document = template.GeneratePdf(Database, target, UserService.CurrentUser, timestamp, state); } Database.SaveChanges(); return File(document, "application/pdf", $"{template.Id}_{target.AttachmentReferenceId.Replace('\\', '_')}_{timestamp:yyyyMMdd-HHmmss}.pdf"); } [DiscoAuthorize(Claims.Config.DocumentTemplate.Delete)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult Delete(string id, bool? redirect = false) { try { var at = Database.DocumentTemplates.Include("JobSubTypes").FirstOrDefault(a => a.Id == id); if (at != null) { at.Delete(Database); Database.SaveChanges(); if (redirect.HasValue && redirect.Value) return RedirectToAction(MVC.Config.DocumentTemplate.Index(null)); else return Ok(); } throw new Exception("Invalid Document Template Id"); } catch (Exception ex) { if (redirect.HasValue && redirect.Value) throw; else return BadRequest(ex.Message); } } [DiscoAuthorizeAll(Claims.Config.DocumentTemplate.Configure, Claims.Config.UserFlag.Configure)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult RemoveOnImportUserFlagRule([Required] string id, Guid? ruleId = null) { try { var template = Database.DocumentTemplates.FirstOrDefault(t => t.Id == id); if (template == null) throw new ArgumentException("Unknown document template", nameof(id)); template.RemoveOnImportUserFlagRule(Database, ruleId.Value); return Ok(); } catch (Exception ex) { return BadRequest(ex.Message); } } [DiscoAuthorizeAll(Claims.Config.DocumentTemplate.Configure, Claims.Config.UserFlag.Configure)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult AddOnImportUserFlagRule([Required] string id, bool? addFlag = null, int? userFlagId = null, string comments = null) { try { var template = Database.DocumentTemplates.FirstOrDefault(t => t.Id == id); if (template == null) throw new ArgumentException("Unknown document template", nameof(id)); var rule = new OnImportUserFlagRule() { AddFlag = addFlag.Value, FlagId = userFlagId.Value, UserId = Authorization.User.UserId, Comments = comments, }; rule = template.AddOnImportUserFlagRule(Database, rule); var model = new AddOnImportUserFlagRuleModel() { Id = rule.Id, FlagId = rule.FlagId, UserId = rule.UserId, AddFlag = rule.AddFlag, Comments = rule.Comments, UserFlagName = rule.UserFlag.Name, UserFlagIcon = rule.UserFlag.Icon, UserFlagColour = rule.UserFlag.IconColour, }; return Json(model); } catch (Exception ex) { return BadRequest(ex.Message); } } [DiscoAuthorize(Claims.Config.DocumentTemplate.Configure)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult BulkDownload([Required] string id, bool? latestOnly = null, DateTime? threshold = null) { var template = Database.DocumentTemplates.FirstOrDefault(t => t.Id == id) ?? throw new ArgumentException("Unknown document template", nameof(id)); var attachments = BulkDownloadRetrieveAttachments(template, latestOnly ?? false, threshold); var responseStream = new MemoryStream(); using (var archive = new ZipArchive(responseStream, ZipArchiveMode.Create, true)) { foreach (var attachment in attachments) { var repoFileName = attachment.RepositoryFilename(Database); if (System.IO.File.Exists(repoFileName)) { var entry = archive.CreateEntry($"{attachment.Reference.ToString().Replace('\\', '_')}-{attachment.Timestamp:yyyyMMdd-HHmmss}_{attachment.Filename}", CompressionLevel.Fastest); entry.LastWriteTime = attachment.Timestamp; using (var entryStream = entry.Open()) { using (var attachmentStream = System.IO.File.OpenRead(repoFileName)) { attachmentStream.CopyTo(entryStream); } } } } } responseStream.Position = 0; return File(responseStream, "application/zip", $"{template.Id}_Attachments_{DateTime.Now:yyyyMMdd-HHmmss}.zip"); } private List BulkDownloadRetrieveAttachments(DocumentTemplate template, bool latestOnly, DateTime? threshold) { List attachments; switch (template.Scope) { case DocumentTemplate.DocumentTemplateScopes.Device: Authorization.Require(Claims.Device.ShowAttachments); var deviceQuery = Database.DeviceAttachments .Where(a => a.DocumentTemplateId == template.Id); if (threshold.HasValue) deviceQuery = deviceQuery.Where(a => a.Timestamp >= threshold.Value); attachments = deviceQuery.OrderBy(a => a.Timestamp).ToList(); break; case DocumentTemplate.DocumentTemplateScopes.Job: Authorization.Require(Claims.Job.ShowAttachments); var jobQuery = Database.JobAttachments .Where(a => a.DocumentTemplateId == template.Id); if (threshold.HasValue) jobQuery = jobQuery.Where(a => a.Timestamp >= threshold.Value); attachments = jobQuery.OrderBy(a => a.Timestamp).ToList(); break; case DocumentTemplate.DocumentTemplateScopes.User: Authorization.Require(Claims.User.ShowAttachments); var userQuery = Database.UserAttachments .Where(a => a.DocumentTemplateId == template.Id); if (threshold.HasValue) userQuery = userQuery.Where(a => a.Timestamp >= threshold.Value); attachments = userQuery.OrderBy(a => a.Timestamp).ToList(); break; default: throw new NotSupportedException(); } if (latestOnly) { attachments.Reverse(); attachments = attachments.GroupBy(a => a.Reference).Select(a => a.First()).OrderBy(a => a.Timestamp).ToList(); } return attachments; } #endregion #region Handlers [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult GenerateDocumentHandlerUi(string templateId, string targetId, string handlerId) { Disco.Services.DocumentTemplateExtensions.GetTemplateAndTarget(Database, Authorization, templateId, targetId, out var template, out var target, out var targetUser); var handlerManifest = Plugins.GetPluginFeature(handlerId, typeof(DocumentHandlerProviderFeature)); using (var handler = handlerManifest.CreateInstance()) { if (!handler.CanHandle(template, 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(template, target, targetUser, CurrentUser); return this.PrecompiledPartialView(handlerPartialView, model); } } [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult DocumentHandlers(string templateId, string targetId) { Disco.Services.DocumentTemplateExtensions.GetTemplateAndTarget(Database, Authorization, templateId, targetId, out var template, out var target, out _); var handlers = Plugins.GetPluginFeatures(typeof(DocumentHandlerProviderFeature)) .SelectMany(f => { using (var handler = f.CreateInstance()) { if (handler.CanHandle(template, target)) return OneOf.Create(new DocumentHandlerModel() { Id = f.Id, Title = handler.HandlerTitle, Description = handler.HandlerDescription, UiUrl = handler.GenerationOptionsUi == null ? null : Url.Action(MVC.API.DocumentTemplate.GenerateDocumentHandlerUi(template.Id, target.AttachmentReferenceId, f.Id)), Icon = handler.GenerationOptionsIcon, }); } return Enumerable.Empty(); }).ToList(); var model = new DocumentHandlersModel() { TemplateId = template.Id, TemplateName = template.Description, TargetId = target.AttachmentReferenceId, TargetName = target.ToString(), Handlers = handlers, }; return Json(model); } #endregion #region Exporting [DiscoAuthorize(Claims.Config.DocumentTemplate.Export)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult Export(ExportModel model) { if (model == null || model.Options == null) throw new ArgumentNullException(nameof(model)); var templateId = default(string); if (model.Options.DocumentTemplateIds.Count == 1) templateId = model.Options.DocumentTemplateIds.First(); Database.DiscoConfiguration.Documents.LastExportOptions = model.Options; Database.SaveChanges(); // Start Export var exportContext = new DocumentExport(model.Options); var taskContext = ExportTask.ScheduleNowCacheResult(exportContext, id => Url.Action(MVC.Config.DocumentTemplate.Export(null, id))); // Try waiting for completion if (taskContext.TaskStatus.WaitUntilFinished(TimeSpan.FromSeconds(2))) return RedirectToAction(MVC.Config.DocumentTemplate.Export(null, taskContext.Id)); else return RedirectToAction(MVC.Config.Logging.TaskStatus(taskContext.TaskStatus.SessionId)); } [DiscoAuthorize(Claims.Config.DocumentTemplate.Export)] public virtual ActionResult ExportRetrieve(Guid id) { if (!ExportTask.TryFromCache(id, out var context)) throw new ArgumentException("The export id specified is invalid, or the export data expired (60 minutes)", nameof(id)); if (context.Result == null || context.Result.Result == null) throw new ArgumentException("The export session is still running, or failed to complete successfully", nameof(id)); if (context.Result.RecordCount == 0) throw new ArgumentException("No records were found to export", nameof(id)); var fileStream = context.Result.Result; return this.File(fileStream.GetBuffer(), 0, (int)fileStream.Length, context.Result.MimeType, context.Result.Filename); } [DiscoAuthorizeAll(Claims.Config.ManageSavedExports, Claims.Config.DocumentTemplate.Export)] [HttpPost, ValidateAntiForgeryToken] public virtual ActionResult SaveExport(ExportModel model) { Database.DiscoConfiguration.Documents.LastExportOptions = model.Options; var export = new DocumentExport(model.Options); var savedExport = SavedExports.SaveExport(export, Database, CurrentUser); return RedirectToAction(MVC.Config.Export.Create(savedExport.Id)); } #endregion } }