private void CheckAvailability(List <SearchResultItem> results) { if (results.Count == 0) { return; } if (!cmsConfiguration.Security.AccessControlEnabled) { // If security is not enabled, all content is available return; } // Create query var principal = securityService.GetCurrentPrincipal(); var urls = results.Select(r => r.Link).ToArray(); var query = repository.AsQueryable <Page>(p => urls.Contains(p.PageUrl)); var pages = query .SelectMany(c => c.AccessRules, (page, accessRule) => new { PageUrl = page.PageUrl, AccessRule = accessRule }) .ToList(); foreach (var pageUrl in pages.Select(p => p.PageUrl).Distinct()) { var page = pages.First(p => p.PageUrl == pageUrl); IList <IAccessRule> accessRules = pages.Where(p => p.PageUrl == pageUrl).Select(p => p.AccessRule).Cast <IAccessRule>().ToList(); var level = accessControlService.GetAccessLevel(accessRules, principal); if (level < AccessLevel.Read) { results.Where(r => r.Link == page.PageUrl).ToList().ForEach(r => r.IsDenied = true); } } results.ForEach(p => { if (p.IsDenied) { p.Link = string.Empty; p.FormattedUrl = LuceneGlobalization.SearchResults_Secured_LinkTitle; p.Title = LuceneGlobalization.SearchResults_Secured_Title; p.Snippet = LuceneGlobalization.SearchResults_Secured_Snippet; } }); }
/// <summary> /// Gets the denied sitemaps. /// </summary> /// <returns>Denied Sitemaps it list.</returns> private IEnumerable <Guid> GetDeniedSitemaps() { var query = Repository.AsQueryable <Models.Sitemap>().Where(f => f.AccessRules.Any(b => b.AccessLevel == AccessLevel.Deny)).FetchMany(f => f.AccessRules); var list = query.ToList().Distinct(); var principle = securityService.GetCurrentPrincipal(); foreach (var sitemap in list) { var accessLevel = accessControlService.GetAccessLevel(sitemap, principle); if (accessLevel == AccessLevel.Deny) { yield return(sitemap.Id); } } }
private bool HasCurrentPrincipalAccess(IAccessSecuredObject page) { if (!cmsConfiguration.Security.AccessControlEnabled) { return(true); } if (accessControlService == null) { return(true); } var principal = SecurityService.GetCurrentPrincipal(); var accessLevel = accessControlService.GetAccessLevel(page, principal); return(accessLevel != AccessLevel.Deny); }
private bool HasAccess(Guid pageId) { if (!cmsConfiguration.AccessControlEnabled) { return(true); } if (accessControlService == null) { return(true); } var principal = SecurityService.GetCurrentPrincipal(); var accessLevel = accessControlService.GetAccessLevel(pageId, principal); return(accessLevel != AccessLevel.Deny); }
private IEnumerable <Guid> GetDeniedPages(PagesFilter request) { var query = Repository.AsQueryable <Root.Models.Page>() .Where(f => f.AccessRules.Any(b => b.AccessLevel == AccessLevel.Deny)) .FetchMany(f => f.AccessRules); var list = query.ToList().Distinct(); var principle = securityService.GetCurrentPrincipal(); foreach (var page in list) { var accessLevel = accessControlService.GetAccessLevel(page, principle); if (accessLevel == AccessLevel.Deny) { yield return(page.Id); } } }
/// <summary> /// Executes the specified id. /// </summary> /// <param name="id">The id.</param> /// <returns> /// Response type of <see cref="DownloadFileCommandResponse" /> /// </returns> /// <exception cref="System.Web.HttpException">403;Access Forbidden</exception> public DownloadFileCommandResponse Execute(Guid id) { // Load file var file = Repository .AsQueryable <MediaFile>(f => f.Id == id && !f.IsDeleted) .Select(f => new { FileUri = f.FileUri, Title = f.Title, OriginalFileExtension = f.OriginalFileExtension, Type = f.Type, PublicUrl = f.PublicUrl }) .FirstOne(); // Access control is ALWAYS disabled for images var accesControlEnabled = cmsConfiguration.AccessControlEnabled && file.Type != MediaType.Image; if (!accesControlEnabled || !storageService.SecuredUrlsEnabled) { return(new DownloadFileCommandResponse { RedirectUrl = file.PublicUrl }); } // Get download URL with security token var principal = SecurityService.GetCurrentPrincipal(); var accessLevel = accessControlService.GetAccessLevel(id, principal); if (accessLevel == AccessLevel.Deny) { throw new HttpException(403, "Access Forbidden"); } var url = storageService.GetSecuredUrl(file.FileUri); return(new DownloadFileCommandResponse { RedirectUrl = url }); }
/// <summary> /// Gets the list of denied pages ids. /// </summary> /// <param name="useCache"></param> /// <returns> /// Enumerable list of denied pages ids /// </returns> public IEnumerable <Guid> GetDeniedPages(bool useCache = true) { IEnumerable <Root.Models.Page> list; var principal = securityService.GetCurrentPrincipal(); if (useCache) { var cacheKey = string.Format("CMS_DeniedPages_{0}_C9E7517250F64F84ADC8-B991C8391306", principal.Identity.Name); list = cacheService.Get(cacheKey, new TimeSpan(0, 0, 0, 30), LoadDeniedPages); } else { list = LoadDeniedPages(); } foreach (var page in list) { var accessLevel = accessControlService.GetAccessLevel(page, principal); if (accessLevel == AccessLevel.Deny) { yield return(page.Id); } } }
/// <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> /// Deletes the page. /// </summary> /// <param name="model">The model.</param> /// <param name="principal">The principal.</param> /// <param name="messages">The messages.</param> /// <returns> /// Delete result /// </returns> /// <exception cref="ConcurrentDataException"></exception> /// <exception cref="System.ComponentModel.DataAnnotations.ValidationException"> /// </exception> public bool DeletePage(DeletePageViewModel model, IPrincipal principal, IMessagesIndicator messages = null) { var languagesFuture = repository.AsQueryable <Language>().ToFuture(); var page = repository .AsQueryable <PageProperties>(p => p.Id == model.PageId) .FetchMany(p => p.PageContents) .ThenFetch(pc => pc.Content) .ToFuture() .FirstOne(); if (model.Version > 0 && page.Version != model.Version) { throw new ConcurrentDataException(page); } if (page.IsMasterPage && repository.AsQueryable <MasterPage>(mp => mp.Master == page).Any()) { var message = PagesGlobalization.DeletePageCommand_MasterPageHasChildren_Message; var logMessage = string.Format("Failed to delete page. Page is selected as master page. Id: {0} Url: {1}", page.Id, page.PageUrl); throw new ValidationException(() => message, logMessage); } var isRedirectInternal = false; if (!string.IsNullOrWhiteSpace(model.RedirectUrl)) { isRedirectInternal = urlService.ValidateInternalUrl(model.RedirectUrl); if (!isRedirectInternal && urlService.ValidateInternalUrl(urlService.FixUrl(model.RedirectUrl))) { isRedirectInternal = true; } if (isRedirectInternal) { model.RedirectUrl = urlService.FixUrl(model.RedirectUrl); } } if (model.UpdateSitemap) { accessControlService.DemandAccess(principal, RootModuleConstants.UserRoles.EditContent); } var sitemaps = new Dictionary <Sitemap, bool>(); var sitemapNodes = sitemapService.GetNodesByPage(page); if (model.UpdateSitemap) { sitemapNodes.Select(node => node.Sitemap) .Distinct() .ToList() .ForEach( sitemap => sitemaps.Add( sitemap, !cmsConfiguration.Security.AccessControlEnabled || accessControlService.GetAccessLevel(sitemap, principal) == AccessLevel.ReadWrite)); foreach (var node in sitemapNodes) { if (sitemaps[node.Sitemap] && node.ChildNodes.Count > 0) { var logMessage = string.Format("In {0} sitemap node {1} has {2} child nodes.", node.Sitemap.Id, node.Id, node.ChildNodes.Count); throw new ValidationException(() => PagesGlobalization.DeletePageCommand_SitemapNodeHasChildNodes_Message, logMessage); } } } unitOfWork.BeginTransaction(); // Update sitemap nodes IList <SitemapNode> updatedNodes = new List <SitemapNode>(); IList <SitemapNode> deletedNodes = new List <SitemapNode>(); UpdateSitemapNodes(model, page, sitemapNodes, sitemaps, languagesFuture.ToList(), updatedNodes, deletedNodes); Redirect redirect; if (!string.IsNullOrWhiteSpace(model.RedirectUrl)) { if (string.Equals(page.PageUrl, model.RedirectUrl, StringComparison.OrdinalIgnoreCase)) { var logMessage = string.Format("Circular redirect loop from url {0} to url {0}.", model.RedirectUrl); throw new ValidationException(() => PagesGlobalization.ValidatePageUrlCommand_SameUrlPath_Message, logMessage); } // Validate url if (!urlService.ValidateExternalUrl(model.RedirectUrl)) { var logMessage = string.Format("Invalid redirect url {0}.", model.RedirectUrl); throw new ValidationException(() => PagesGlobalization.ValidatePageUrlCommand_InvalidUrlPath_Message, logMessage); } string patternsValidationMessage; if (isRedirectInternal && !urlService.ValidateUrlPatterns(model.RedirectUrl, out patternsValidationMessage, PagesGlobalization.DeletePage_RedirectUrl_Name)) { var logMessage = string.Format("{0}. URL: {1}.", patternsValidationMessage, model.RedirectUrl); throw new ValidationException(() => patternsValidationMessage, logMessage); } redirect = redirectService.GetPageRedirect(page.PageUrl); if (redirect != null) { redirect.RedirectUrl = model.RedirectUrl; } else { redirect = redirectService.CreateRedirectEntity(page.PageUrl, model.RedirectUrl); } if (redirect != null) { repository.Save(redirect); } } else { redirect = null; } // Delete child entities. if (page.PageTags != null) { foreach (var pageTag in page.PageTags) { repository.Delete(pageTag); } } var deletedPageContents = new List <PageContent>(); var htmlContentsToDelete = new List <HtmlContent>(); if (page.PageContents != null) { foreach (var pageContent in page.PageContents) { // If content is HTML content, delete HTML content var htmlContent = pageContent.Content as HtmlContent; if (htmlContent != null) { var draft = pageContent.Content.History != null?pageContent.Content.History.FirstOrDefault(c => c.Status == ContentStatus.Draft) : null; if (draft != null) { repository.Delete(draft); } repository.Delete(htmlContent); htmlContentsToDelete.Add(htmlContent); } repository.Delete(pageContent); deletedPageContents.Add(pageContent); } } if (page.Options != null) { foreach (var option in page.Options) { repository.Delete(option); } } if (page.AccessRules != null) { var rules = page.AccessRules.ToList(); rules.ForEach(page.RemoveRule); } if (page.MasterPages != null) { foreach (var master in page.MasterPages) { repository.Delete(master); } } // Delete page repository.Delete <Root.Models.Page>(page); // Commit unitOfWork.Commit(); var updatedSitemaps = new List <Sitemap>(); foreach (var node in updatedNodes) { Events.SitemapEvents.Instance.OnSitemapNodeUpdated(node); if (!updatedSitemaps.Contains(node.Sitemap)) { updatedSitemaps.Add(node.Sitemap); } } foreach (var node in deletedNodes) { Events.SitemapEvents.Instance.OnSitemapNodeDeleted(node); if (!updatedSitemaps.Contains(node.Sitemap)) { updatedSitemaps.Add(node.Sitemap); } } foreach (var updatedSitemap in updatedSitemaps) { Events.SitemapEvents.Instance.OnSitemapUpdated(updatedSitemap); } // Notifying about redirect created if (redirect != null) { Events.PageEvents.Instance.OnRedirectCreated(redirect); } // Notify about deleted page contents foreach (var deletedPageContent in deletedPageContents) { Events.PageEvents.Instance.OnPageContentDeleted(deletedPageContent); } // Notify about deleted html contents foreach (var htmlContent in htmlContentsToDelete) { Events.PageEvents.Instance.OnHtmlContentDeleted(htmlContent); } // Notifying, that page is deleted. Events.PageEvents.Instance.OnPageDeleted(page); if (sitemaps.Any(tuple => !tuple.Value) && messages != null) { // Some sitemaps where skipped, because user has no permission to edit. messages.AddSuccess(PagesGlobalization.DeletePage_SitemapSkipped_Message); } return(true); }
/// <summary> /// Saves the blog post. /// </summary> /// <param name="request">The request.</param> /// <param name="childContentOptionValues">The child content option values.</param> /// <param name="principal">The principal.</param> /// <param name="errorMessages">The error messages.</param> /// <returns> /// Saved blog post entity /// </returns> /// <exception cref="System.ComponentModel.DataAnnotations.ValidationException"></exception> /// <exception cref="SecurityException">Forbidden: Access is denied.</exception> public BlogPost SaveBlogPost(BlogPostViewModel request, IList <ContentOptionValuesViewModel> childContentOptionValues, IPrincipal principal, out string[] errorMessages) { errorMessages = new string[0]; 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 }; } var isNew = request.Id.HasDefaultValue(); var userCanEdit = securityService.IsAuthorized(RootModuleConstants.UserRoles.EditContent); ValidateData(isNew, request); BlogPost blogPost; BlogPostContent content; PageContent pageContent; GetBlogPostAndContentEntities(request, principal, roles, ref isNew, out content, out pageContent, out blogPost); var beforeChange = new UpdatingBlogModel(blogPost); // Master page / layout Layout layout; Page masterPage; Region region; LoadDefaultLayoutAndRegion(out layout, out masterPage, out region); 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); } } if (pageContent.Region == null) { pageContent.Region = region; } // Load master pages for updating page's master path and page's children master path IList <Guid> newMasterIds; IList <Guid> oldMasterIds; IList <Guid> childrenPageIds; IList <MasterPage> existingChildrenMasterPages; PrepareForUpdateChildrenMasterPages(isNew, blogPost, request, out newMasterIds, out oldMasterIds, out childrenPageIds, out existingChildrenMasterPages); // TODO: TEST AND TRY TO FIX IT: TRANSACTION HERE IS REQUIRED! // UnitOfWork.BeginTransaction(); // NOTE: this causes concurrent data exception. Redirect redirectCreated = null; if (!isNew && userCanEdit && !string.Equals(blogPost.PageUrl, request.BlogUrl) && !string.IsNullOrWhiteSpace(request.BlogUrl)) { 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); } // Push to change modified data each time. blogPost.ModifiedOn = DateTime.Now; 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, null, blogPost.Category != null ? (Guid?)blogPost.Category.Id : null); } 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 = SaveContentWithStatusUpdate(isNew, newContent, request, principal); pageContent.Content = content; optionService.SaveChildContentOptions(content, childContentOptionValues, request.DesirableStatus); blogPost.PageUrlHash = blogPost.PageUrl.UrlHash(); blogPost.UseCanonicalUrl = request.UseCanonicalUrl; MapExtraProperties(isNew, blogPost, content, pageContent, request, principal); // Notify about page properties changing. var cancelEventArgs = Events.BlogEvents.Instance.OnBlogChanging(beforeChange, new UpdatingBlogModel(blogPost)); if (cancelEventArgs.Cancel) { errorMessages = cancelEventArgs.CancellationErrorMessages.ToArray(); return(null); } repository.Save(blogPost); repository.Save(content); repository.Save(pageContent); masterPageService.UpdateChildrenMasterPages(existingChildrenMasterPages, oldMasterIds, newMasterIds, childrenPageIds); pageContent.Content = content; blogPost.PageContents = new [] { pageContent }; IList <Tag> newTags = null; if (userCanEdit) { newTags = SaveTags(blogPost, request); } // Commit unitOfWork.Commit(); // Notify about new created tags. Events.RootEvents.Instance.OnTagCreated(newTags); // Notify about new or updated blog post. if (isNew) { Events.BlogEvents.Instance.OnBlogCreated(blogPost); } else { Events.BlogEvents.Instance.OnBlogUpdated(blogPost); } // Notify about redirect creation. if (redirectCreated != null) { Events.PageEvents.Instance.OnRedirectCreated(redirectCreated); } return(blogPost); }