/// <summary> /// Saves the blog post. /// </summary> /// <param name="request">The request.</param> /// <param name="principal">The principal.</param> /// <returns> /// Saved blog post entity /// </returns> public BlogPost SaveBlogPost(BlogPostViewModel request, IPrincipal principal) { string[] roles; if (request.DesirableStatus == ContentStatus.Published) { accessControlService.DemandAccess(principal, RootModuleConstants.UserRoles.PublishContent); roles = new[] { RootModuleConstants.UserRoles.PublishContent }; } else { accessControlService.DemandAccess(principal, RootModuleConstants.UserRoles.EditContent); roles = new[] { RootModuleConstants.UserRoles.EditContent }; } Layout layout; Page masterPage; LoadLayout(out layout, out masterPage); if (masterPage != null) { var level = accessControlService.GetAccessLevel(masterPage, principal); if (level < AccessLevel.Read) { var message = BlogGlobalization.SaveBlogPost_FailedToSave_InaccessibleMasterPage; const string logMessage = "Failed to save blog post. Selected master page for page layout is inaccessible."; throw new ValidationException(() => message, logMessage); } } var region = LoadRegion(layout, masterPage); var isNew = request.Id.HasDefaultValue(); var userCanEdit = securityService.IsAuthorized(RootModuleConstants.UserRoles.EditContent); // UnitOfWork.BeginTransaction(); // NOTE: this causes concurrent data exception. BlogPost blogPost; BlogPostContent content = null; PageContent pageContent = null; Pages.Models.Redirect redirectCreated = null; // Loading blog post and it's content, or creating new, if such not exists if (!isNew) { var blogPostFuture = repository .AsQueryable <BlogPost>(b => b.Id == request.Id) .ToFuture(); content = repository .AsQueryable <BlogPostContent>(c => c.PageContents.Any(x => x.Page.Id == request.Id && !x.IsDeleted)) .ToFuture() .FirstOrDefault(); blogPost = blogPostFuture.FirstOne(); if (cmsConfiguration.Security.AccessControlEnabled) { accessControlService.DemandAccess(blogPost, principal, AccessLevel.ReadWrite, roles); } if (content != null) { // Check if user has confirmed the deletion of content if (!request.IsUserConfirmed && blogPost.IsMasterPage) { var hasAnyChildren = contentService.CheckIfContentHasDeletingChildren(blogPost.Id, content.Id, request.Content); if (hasAnyChildren) { var message = PagesGlobalization.SaveContent_ContentHasChildrenContents_RegionDeleteConfirmationMessage; var logMessage = string.Format("User is trying to delete content regions which has children contents. Confirmation is required. ContentId: {0}, PageId: {1}", content.Id, blogPost.Id); throw new ConfirmationRequestException(() => message, logMessage); } } pageContent = repository.FirstOrDefault <PageContent>(c => c.Page == blogPost && !c.IsDeleted && c.Content == content); } if (userCanEdit && !string.Equals(blogPost.PageUrl, request.BlogUrl) && request.BlogUrl != null) { request.BlogUrl = urlService.FixUrl(request.BlogUrl); pageService.ValidatePageUrl(request.BlogUrl, request.Id); if (request.RedirectFromOldUrl) { var redirect = redirectService.CreateRedirectEntity(blogPost.PageUrl, request.BlogUrl); if (redirect != null) { repository.Save(redirect); redirectCreated = redirect; } } blogPost.PageUrl = urlService.FixUrl(request.BlogUrl); } } else { blogPost = new BlogPost(); } if (pageContent == null) { pageContent = new PageContent { Region = region, Page = blogPost }; } // Push to change modified data each time. blogPost.ModifiedOn = DateTime.Now; blogPost.Version = request.Version; if (userCanEdit) { blogPost.Title = request.Title; blogPost.Description = request.IntroText; blogPost.Author = request.AuthorId.HasValue ? repository.AsProxy <Author>(request.AuthorId.Value) : null; blogPost.Category = request.CategoryId.HasValue ? repository.AsProxy <Category>(request.CategoryId.Value) : null; blogPost.Image = (request.Image != null && request.Image.ImageId.HasValue) ? repository.AsProxy <MediaImage>(request.Image.ImageId.Value) : null; if (isNew || request.DesirableStatus == ContentStatus.Published) { blogPost.ActivationDate = request.LiveFromDate; blogPost.ExpirationDate = TimeHelper.FormatEndDate(request.LiveToDate); } } if (isNew) { if (!string.IsNullOrWhiteSpace(request.BlogUrl)) { blogPost.PageUrl = urlService.FixUrl(request.BlogUrl); pageService.ValidatePageUrl(blogPost.PageUrl); } else { blogPost.PageUrl = CreateBlogPermalink(request.Title); } blogPost.MetaTitle = request.MetaTitle ?? request.Title; if (masterPage != null) { blogPost.MasterPage = masterPage; masterPageService.SetPageMasterPages(blogPost, masterPage.Id); } else { blogPost.Layout = layout; } UpdateStatus(blogPost, request.DesirableStatus); AddDefaultAccessRules(blogPost, principal, masterPage); } else if (request.DesirableStatus == ContentStatus.Published || blogPost.Status == PageStatus.Preview) { // Update only if publishing or current status is preview. // Else do not change, because it may change from published to draft status UpdateStatus(blogPost, request.DesirableStatus); } // Create content. var newContent = new BlogPostContent { Id = content != null ? content.Id : Guid.Empty, Name = request.Title, Html = request.Content ?? string.Empty, EditInSourceMode = request.EditInSourceMode, ActivationDate = request.LiveFromDate, ExpirationDate = TimeHelper.FormatEndDate(request.LiveToDate) }; // Preserve content if user is not authorized to change it. if (!userCanEdit) { if (content == null) { throw new SecurityException("Forbidden: Access is denied."); // User has no rights to create new content. } var contentToPublish = (BlogPostContent)(content.History != null ? content.History.FirstOrDefault(c => c.Status == ContentStatus.Draft) ?? content : content); newContent.Name = contentToPublish.Name; newContent.Html = contentToPublish.Html; } content = (BlogPostContent)contentService.SaveContentWithStatusUpdate(newContent, request.DesirableStatus); pageContent.Content = content; blogPost.PageUrlHash = blogPost.PageUrl.UrlHash(); blogPost.UseCanonicalUrl = request.UseCanonicalUrl; repository.Save(blogPost); repository.Save(content); repository.Save(pageContent); pageContent.Content = content; blogPost.PageContents = new [] { pageContent }; IList <Tag> newTags = null; if (userCanEdit) { tagService.SavePageTags(blogPost, request.Tags, out newTags); } // Commit unitOfWork.Commit(); // Notify about new or updated blog post. if (isNew) { Events.BlogEvents.Instance.OnBlogCreated(blogPost); } else { Events.BlogEvents.Instance.OnBlogUpdated(blogPost); } // Notify about new created tags. Events.RootEvents.Instance.OnTagCreated(newTags); // Notify about redirect creation. if (redirectCreated != null) { Events.PageEvents.Instance.OnRedirectCreated(redirectCreated); } return(blogPost); }
/// <summary> /// Executes the specified request. /// </summary> /// <param name="request">The request.</param> /// <returns>Blog post view model</returns> public SaveBlogPostCommandResponse Execute(BlogPostViewModel request) { if (request.DesirableStatus == ContentStatus.Published) { AccessControlService.DemandAccess(Context.Principal, RootModuleConstants.UserRoles.PublishContent); } var layout = LoadLayout(); var region = LoadRegion(); var isNew = request.Id.HasDefaultValue(); var userCanEdit = false; if (isNew || request.DesirableStatus != ContentStatus.Published) { AccessControlService.DemandAccess(Context.Principal, RootModuleConstants.UserRoles.EditContent); userCanEdit = true; } else { userCanEdit = SecurityService.IsAuthorized(RootModuleConstants.UserRoles.EditContent); } // UnitOfWork.BeginTransaction(); // NOTE: this causes concurrent data exception. BlogPost blogPost; BlogPostContent content = null; PageContent pageContent = null; Pages.Models.Redirect redirectCreated = null; // Loading blog post and it's content, or creating new, if such not exists if (!isNew) { blogPost = Repository.First <BlogPost>(request.Id); content = Repository.FirstOrDefault <BlogPostContent>(c => c.PageContents.Any(x => x.Page == blogPost && x.Region == region && !x.IsDeleted)); if (content != null) { pageContent = Repository.FirstOrDefault <PageContent>(c => c.Page == blogPost && c.Region == region && !c.IsDeleted && c.Content == content); } if (userCanEdit && !string.Equals(blogPost.PageUrl, request.BlogUrl) && request.BlogUrl != null) { request.BlogUrl = urlService.FixUrl(request.BlogUrl); pageService.ValidatePageUrl(request.BlogUrl, request.Id); if (request.RedirectFromOldUrl) { var redirect = redirectService.CreateRedirectEntity(blogPost.PageUrl, request.BlogUrl); if (redirect != null) { Repository.Save(redirect); redirectCreated = redirect; } } blogPost.PageUrl = urlService.FixUrl(request.BlogUrl); } } else { blogPost = new BlogPost(); AddDefaultAccessRules(blogPost); } if (pageContent == null) { pageContent = new PageContent { Region = region, Page = blogPost }; } // Push to change modified data each time. blogPost.ModifiedOn = DateTime.Now; blogPost.Version = request.Version; if (userCanEdit) { blogPost.Title = request.Title; blogPost.Description = request.IntroText; blogPost.Author = request.AuthorId.HasValue ? Repository.AsProxy <Author>(request.AuthorId.Value) : null; blogPost.Category = request.CategoryId.HasValue ? Repository.AsProxy <Category>(request.CategoryId.Value) : null; blogPost.Image = (request.Image != null && request.Image.ImageId.HasValue) ? Repository.AsProxy <MediaImage>(request.Image.ImageId.Value) : null; if (isNew || request.DesirableStatus == ContentStatus.Published) { blogPost.ActivationDate = request.LiveFromDate; blogPost.ExpirationDate = TimeHelper.FormatEndDate(request.LiveToDate); } } if (isNew) { if (!string.IsNullOrWhiteSpace(request.BlogUrl)) { blogPost.PageUrl = urlService.FixUrl(request.BlogUrl); pageService.ValidatePageUrl(blogPost.PageUrl); } else { blogPost.PageUrl = blogService.CreateBlogPermalink(request.Title); } blogPost.MetaTitle = request.Title; blogPost.Layout = layout; UpdateStatus(blogPost, request.DesirableStatus); } else if (request.DesirableStatus == ContentStatus.Published) { UpdateStatus(blogPost, request.DesirableStatus); } // Create content. var newContent = new BlogPostContent { Id = content != null ? content.Id : Guid.Empty, Name = request.Title, Html = request.Content ?? string.Empty, EditInSourceMode = request.EditInSourceMode, ActivationDate = request.LiveFromDate, ExpirationDate = TimeHelper.FormatEndDate(request.LiveToDate) }; // Preserve content if user is not authorized to change it. if (!userCanEdit) { if (content == null) { throw new SecurityException("Forbidden: Access is denied."); // User has no rights to create new content. } var contentToPublish = (BlogPostContent)(content.History != null ? content.History.FirstOrDefault(c => c.Status == ContentStatus.Draft) ?? content : content); newContent.Name = contentToPublish.Name; newContent.Html = contentToPublish.Html; } content = (BlogPostContent)contentService.SaveContentWithStatusUpdate(newContent, request.DesirableStatus); pageContent.Content = content; blogPost.PageUrlHash = blogPost.PageUrl.UrlHash(); blogPost.UseCanonicalUrl = request.UseCanonicalUrl; Repository.Save(blogPost); Repository.Save(content); Repository.Save(pageContent); // Save tags if user has edit right. IList <Tag> newTags = null; if (userCanEdit) { tagService.SavePageTags(blogPost, request.Tags, out newTags); } // Commit UnitOfWork.Commit(); // Notify about new or updated blog post. if (isNew) { Events.BlogEvents.Instance.OnBlogCreated(blogPost); } else { Events.BlogEvents.Instance.OnBlogUpdated(blogPost); } // Notify about new created tags. Events.RootEvents.Instance.OnTagCreated(newTags); // Notify about redirect creation. if (redirectCreated != null) { Events.PageEvents.Instance.OnRedirectCreated(redirectCreated); } return(new SaveBlogPostCommandResponse { Id = blogPost.Id, Version = blogPost.Version, Title = blogPost.Title, PageUrl = blogPost.PageUrl, ModifiedByUser = blogPost.ModifiedByUser, ModifiedOn = blogPost.ModifiedOn.ToFormattedDateString(), CreatedOn = blogPost.CreatedOn.ToFormattedDateString(), PageStatus = blogPost.Status, DesirableStatus = request.DesirableStatus, PageContentId = pageContent.Id }); }