/// <summary> /// Saves the given page model /// </summary> /// <param name="model">The page model</param> private async Task <IEnumerable <Guid> > Save <T>(T model, bool isDraft) where T : Models.PageBase { var type = App.PageTypes.GetById(model.TypeId); var affected = new List <Guid>(); var isNew = false; var lastModified = DateTime.MinValue; if (type != null) { // Set content type model.ContentType = type.ContentTypeId; IQueryable <Page> pageQuery = _db.Pages; if (isDraft) { pageQuery = pageQuery.AsNoTracking(); } var page = await pageQuery .Include(p => p.Blocks).ThenInclude(b => b.Block).ThenInclude(b => b.Fields) .Include(p => p.Fields) .FirstOrDefaultAsync(p => p.Id == model.Id) .ConfigureAwait(false); if (page == null) { isNew = true; } else { lastModified = page.LastModified; } if (model.OriginalPageId.HasValue) { var originalPageIsCopy = (await _db.Pages.AsNoTracking().FirstOrDefaultAsync(p => p.Id == model.OriginalPageId).ConfigureAwait(false))?.OriginalPageId.HasValue ?? false; if (originalPageIsCopy) { throw new InvalidOperationException("Can not set copy of a copy"); } var originalPageType = (await _db.Pages.AsNoTracking().FirstOrDefaultAsync(p => p.Id == model.OriginalPageId).ConfigureAwait(false))?.PageTypeId; if (originalPageType != model.TypeId) { throw new InvalidOperationException("Copy can not have a different content type"); } // Transform the model if (page == null) { page = new Page() { Id = model.Id != Guid.Empty ? model.Id : Guid.NewGuid(), }; if (!isDraft) { _db.Pages.Add(page); // Make room for the new page var dest = await _db.Pages.Where(p => p.SiteId == model.SiteId && p.ParentId == model.ParentId).ToListAsync().ConfigureAwait(false); affected.AddRange(MovePages(dest, page.Id, model.SiteId, model.ParentId, model.SortOrder, true)); } } else { // Check if the page has been moved if (!isDraft && (page.ParentId != model.ParentId || page.SortOrder != model.SortOrder)) { var source = await _db.Pages.Where(p => p.SiteId == page.SiteId && p.ParentId == page.ParentId && p.Id != model.Id).ToListAsync().ConfigureAwait(false); var dest = page.ParentId == model.ParentId ? source : await _db.Pages.Where(p => p.SiteId == model.SiteId && p.ParentId == model.ParentId).ToListAsync().ConfigureAwait(false); // Remove the old position for the page affected.AddRange(MovePages(source, page.Id, page.SiteId, page.ParentId, page.SortOrder + 1, false)); // Add room for the new position of the page affected.AddRange(MovePages(dest, page.Id, model.SiteId, model.ParentId, model.SortOrder, true)); } } if (!isDraft && (isNew || page.Title != model.Title || page.NavigationTitle != model.NavigationTitle)) { // If this is new page or title has been updated it means // the global sitemap changes. Notify the service. affected.Add(page.Id); } page.PageTypeId = model.TypeId; page.OriginalPageId = model.OriginalPageId; page.SiteId = model.SiteId; page.Title = model.Title; page.NavigationTitle = model.NavigationTitle; page.Slug = model.Slug; page.ParentId = model.ParentId; page.SortOrder = model.SortOrder; page.IsHidden = model.IsHidden; page.Route = model.Route; page.Published = model.Published; if (!isDraft) { await _db.SaveChangesAsync().ConfigureAwait(false); } else { var draft = await _db.PageRevisions .FirstOrDefaultAsync(r => r.PageId == page.Id && r.Created > lastModified) .ConfigureAwait(false); if (draft == null) { draft = new PageRevision { Id = Guid.NewGuid(), PageId = page.Id }; await _db.PageRevisions .AddAsync(draft) .ConfigureAwait(false); } draft.Data = JsonConvert.SerializeObject(page); draft.Created = page.LastModified; await _db.SaveChangesAsync().ConfigureAwait(false); } return(affected); } // Transform the model if (page == null) { page = new Page { Id = model.Id != Guid.Empty ? model.Id : Guid.NewGuid(), ParentId = model.ParentId, SortOrder = model.SortOrder, PageTypeId = model.TypeId, Created = DateTime.Now, LastModified = DateTime.Now }; model.Id = page.Id; if (!isDraft) { await _db.Pages.AddAsync(page); // Make room for the new page var dest = await _db.Pages.Where(p => p.SiteId == model.SiteId && p.ParentId == model.ParentId).ToListAsync().ConfigureAwait(false); affected.AddRange(MovePages(dest, page.Id, model.SiteId, model.ParentId, model.SortOrder, true)); } } else { // Check if the page has been moved if (!isDraft && (page.ParentId != model.ParentId || page.SortOrder != model.SortOrder)) { var source = await _db.Pages.Where(p => p.SiteId == page.SiteId && p.ParentId == page.ParentId && p.Id != model.Id).ToListAsync().ConfigureAwait(false); var dest = page.ParentId == model.ParentId ? source : await _db.Pages.Where(p => p.SiteId == model.SiteId && p.ParentId == model.ParentId).ToListAsync().ConfigureAwait(false); // Remove the old position for the page affected.AddRange(MovePages(source, page.Id, page.SiteId, page.ParentId, page.SortOrder + 1, false)); // Add room for the new position of the page affected.AddRange(MovePages(dest, page.Id, model.SiteId, model.ParentId, model.SortOrder, true)); } page.LastModified = DateTime.Now; } if (isNew || page.Title != model.Title || page.NavigationTitle != model.NavigationTitle) { // If this is new page or title has been updated it means // the global sitemap changes. Notify the service. affected.Add(page.Id); } page = _contentService.Transform <T>(model, type, page); // Transform blocks var blockModels = model.Blocks; if (blockModels != null) { var blocks = _contentService.TransformBlocks(blockModels); var current = blocks.Select(b => b.Id).ToArray(); // Delete removed blocks var removed = page.Blocks .Where(b => !current.Contains(b.BlockId) && !b.Block.IsReusable && b.Block.ParentId == null) .Select(b => b.Block); var removedItems = page.Blocks .Where(b => !current.Contains(b.BlockId) && b.Block.ParentId != null && removed.Select(p => p.Id).ToList().Contains(b.Block.ParentId.Value)) .Select(b => b.Block); if (!isDraft) { _db.Blocks.RemoveRange(removed); _db.Blocks.RemoveRange(removedItems); } // Delete the old page blocks page.Blocks.Clear(); // Now map the new block for (var n = 0; n < blocks.Count; n++) { IQueryable <Block> blockQuery = _db.Blocks; if (isDraft) { blockQuery = blockQuery.AsNoTracking(); } var block = await blockQuery .Include(b => b.Fields) .FirstOrDefaultAsync(b => b.Id == blocks[n].Id) .ConfigureAwait(false); if (block == null) { block = new Block { Id = blocks[n].Id != Guid.Empty ? blocks[n].Id : Guid.NewGuid(), Created = DateTime.Now }; if (!isDraft) { await _db.Blocks.AddAsync(block).ConfigureAwait(false); } } block.ParentId = blocks[n].ParentId; block.CLRType = blocks[n].CLRType; block.IsReusable = blocks[n].IsReusable; block.Title = blocks[n].Title; block.LastModified = DateTime.Now; var currentFields = blocks[n].Fields.Select(f => f.FieldId).Distinct(); var removedFields = block.Fields.Where(f => !currentFields.Contains(f.FieldId)); if (!isDraft) { _db.BlockFields.RemoveRange(removedFields); } foreach (var newField in blocks[n].Fields) { var field = block.Fields.FirstOrDefault(f => f.FieldId == newField.FieldId); if (field == null) { field = new BlockField { Id = newField.Id != Guid.Empty ? newField.Id : Guid.NewGuid(), BlockId = block.Id, FieldId = newField.FieldId }; if (!isDraft) { await _db.BlockFields.AddAsync(field).ConfigureAwait(false); } block.Fields.Add(field); } field.SortOrder = newField.SortOrder; field.CLRType = newField.CLRType; field.Value = newField.Value; } // Create the page block page.Blocks.Add(new PageBlock { Id = Guid.NewGuid(), BlockId = block.Id, Block = block, PageId = page.Id, SortOrder = n }); } } if (!isDraft) { await _db.SaveChangesAsync().ConfigureAwait(false); } else { var draft = await _db.PageRevisions .FirstOrDefaultAsync(r => r.PageId == page.Id && r.Created > lastModified) .ConfigureAwait(false); if (draft == null) { draft = new PageRevision { Id = Guid.NewGuid(), PageId = page.Id }; await _db.PageRevisions .AddAsync(draft) .ConfigureAwait(false); } draft.Data = JsonConvert.SerializeObject(page); draft.Created = page.LastModified; await _db.SaveChangesAsync().ConfigureAwait(false); } } return(affected); }
public async Task <IActionResult> Revert(int pageId, int revisionId) { try { Page page = await _Db.Pages .Include(p => p.PageRevisions) .ThenInclude(pr => pr.Template) .Include(p => p.PageRevisions) .ThenInclude(pr => pr.RevisionTextComponents) .ThenInclude(rtc => rtc.TextComponent) .Include(p => p.PageRevisions) .ThenInclude(pr => pr.RevisionMediaComponents) .ThenInclude(rmc => rmc.MediaComponent) .Where(p => p.Id == pageId) .FirstOrDefaultAsync(); // Ensure page exists if (page == null) { return(BadRequest("No page exists for given page ID")); } // Ensure that the revision belongs to the correct page if (!page.PageRevisions.Any(pr => pr.Id == revisionId)) { return(BadRequest("Given page does not contain a revision for the given revision ID")); } PageRevision revertTo = page.PageRevisions.Find(pr => pr.Id == revisionId); // Create a new revision for the page, that is a copy of the supplied revision PageRevision newRevision = new PageRevision { Reason = string.Format("Reverted page to revision: {0}", revertTo.Reason), CreatedBy = await _Db.Accounts.FindAsync(User.AccountId()), Page = page, Template = revertTo.Template }; await _Db.AddAsync(newRevision); // Create associations between the existing text components and the new revision foreach (RevisionTextComponent rtc in revertTo.RevisionTextComponents) { await _Db.AddAsync(new RevisionTextComponent { PageRevisionId = newRevision.Id, TextComponentId = rtc.TextComponentId }); } // Do the same for existing media components foreach (RevisionMediaComponent rmc in revertTo.RevisionMediaComponents) { await _Db.AddAsync(new RevisionMediaComponent { PageRevisionId = newRevision.Id, MediaComponentId = rmc.MediaComponentId }); } // Execute the database transaction await _Db.SaveChangesAsync(); _Logger.LogDebug("{0} page reverted to revision {1} ({2}) by {3}", page.Name, revisionId, revertTo.Reason, User.AccountName()); return(Ok()); } catch (Exception ex) { _Logger.LogError("Error reverting page {0} to revision {1}: {2}", pageId, revisionId, ex.Message); _Logger.LogError(ex.StackTrace); return(BadRequest("Something went wrong, please try again later.")); } }
//POST: /admin/pages/create public async Task <IActionResult> Create(IFormCollection request, [Bind("Name", "Description", "Section")] Page page) { PageTemplate template = await _Db.PageTemplates.FindAsync( request.Int("templateId")); if (template == null) { return(NotFound($"The chosen template was not found.")); } if (!ModelState.IsValid) { return(BadRequest("Server side validation failed.")); } try { await _Db.AddAsync(page); // Create initial page revision PageRevision pageRevision = new PageRevision { Page = page, Template = template, CreatedBy = await _Db.Accounts.FindAsync(User.AccountId()) }; await _Db.AddAsync(pageRevision); // Create empty text fields, and associate to new page for (int i = 0; i < template.TextAreas; i++) { TextComponent textField = new TextComponent { SlotNo = i }; await _Db.AddAsync(textField); await _Db.AddAsync(new RevisionTextComponent { TextComponent = textField, PageRevision = pageRevision, }); } // Create empty media fields, and associate to new page for (int i = 0; i < template.MediaAreas; i++) { MediaComponent mediaComponent = new MediaComponent { SlotNo = i }; await _Db.AddAsync(mediaComponent); await _Db.AddAsync(new RevisionMediaComponent { PageRevisionId = pageRevision.Id, MediaComponentId = mediaComponent.Id }); } // Save all to database in one transaction await _Db.SaveChangesAsync(); // Return new page URL to the caller return(Ok(page.AbsoluteUrl)); } catch (Exception ex) { _Logger.LogWarning("Error creating new page: {0}", ex.Message); _Logger.LogWarning(ex.StackTrace); return(BadRequest("There was an error creating the page. Please try again later.")); } }
public async Task <IActionResult> CreateRevision(int pageId, IFormCollection request) { /* To be supplied in request body - * * Reason for change - e.g. Updated pricing information * Template ID * Collection of text components * Collection of media components * */ try { // Load page from database Page page = await _Db.Pages.FindAsync(pageId); if (page == null) { return(BadRequest("No page exists for given page ID")); } List <TextComponent> newRevisionTextComponents = request.Deserialize(typeof(List <TextComponent>), "textComponents"); List <MediaComponent> newRevisionMediaComponents = request.Deserialize(typeof(List <MediaComponent>), "imageComponents"); // Todo: Do this after the view has had template switching enabled // Load template from database // PageTemplate template = await _Db.PageTemplates // .FindAsync(request.Int("templateId")); // Fetch the original revision PageRevision old = await _Db.PageRevisions .Include(pr => pr.Template) .Include(pr => pr.RevisionMediaComponents) .ThenInclude(rmc => rmc.MediaComponent) .Include(pr => pr.RevisionTextComponents) .ThenInclude(rtc => rtc.TextComponent) .ThenInclude(tc => tc.CmsButton) .Where(pr => pr.Page == page) .OrderByDescending(pr => pr.CreatedAt) .FirstOrDefaultAsync(); var oldRevision = new { old.Template, TextComponents = old.RevisionTextComponents .Select(rtc => rtc.TextComponent) .OrderBy(tc => tc.SlotNo) .ToList(), MediaComponents = old.RevisionMediaComponents .Select(rmc => rmc.MediaComponent) .OrderBy(tc => tc.SlotNo) .ToList() }; // Create the new page revision PageRevision newRevision = new PageRevision { Page = page, Template = oldRevision.Template, Reason = request.Str("reason"), CreatedBy = await _Db.Accounts.FindAsync(User.AccountId()), }; // Assign the new revision an ID await _Db.AddAsync(newRevision); for (int i = 0; i < newRevision.Template.TextAreas; i++) { TextComponent textComponentToSave = null; // Only save a new text component if it has changed if (!newRevisionTextComponents[i].Equals(oldRevision.TextComponents[i])) { textComponentToSave = newRevisionTextComponents[i]; // Set ID to 0 so that EF Core assigns us a new one textComponentToSave.Id = 0; // Save a new button if the components button does not yet exist in database. if (textComponentToSave.CmsButton != null && !textComponentToSave.CmsButton.Equals(oldRevision.TextComponents[i].CmsButton)) { await _Db.AddAsync(textComponentToSave.CmsButton); } // Generate ID for the new TextComponent await _Db.AddAsync(textComponentToSave); } // Add association between component and new revision await _Db.AddAsync(new RevisionTextComponent { // Use the new components ID if it exists, other use existing (unchanged) component // from previous revision TextComponentId = textComponentToSave?.Id ?? oldRevision.TextComponents[i].Id, PageRevisionId = newRevision.Id }); } // Do the same for media components for (int i = 0; i < newRevision.Template.MediaAreas; i++) { MediaComponent mediaComponentToSave = null; // Only create new media component if the old one was modified if (!newRevisionMediaComponents[i].Equals(oldRevision.MediaComponents[i])) { mediaComponentToSave = newRevisionMediaComponents[i]; // Generate new ID mediaComponentToSave.Id = 0; await _Db.AddAsync(mediaComponentToSave); } // Add association to new revision await _Db.AddAsync(new RevisionMediaComponent { PageRevisionId = newRevision.Id, MediaComponentId = mediaComponentToSave?.Id ?? oldRevision.MediaComponents[i].Id }); } // Save changes await _Db.SaveChangesAsync(); _Logger.LogDebug("New page revision created for page {0} ({1}): {2}", pageId, page.Name, newRevision.Reason); return(Ok()); } catch (Exception ex) { _Logger.LogError("Error creating new revision for page {0}: {1}", pageId, ex.Message); _Logger.LogError(ex.StackTrace); return(BadRequest("Something went wrong, please try again later.")); } }