/// <summary> /// Executes the specified request. /// </summary> /// <param name="request">The request.</param> /// <returns>Save response.</returns> /// <exception cref="CmsException">Failed to save page properties.</exception> public SavePageResponse Execute(EditPagePropertiesViewModel request) { var isMultilanguageEnabled = cmsConfiguration.EnableMultilanguage; ValidateRequest(request, isMultilanguageEnabled); var pageQuery = Repository.AsQueryable <PageProperties>(p => p.Id == request.Id) .FetchMany(p => p.Options) .Fetch(p => p.Layout) .ThenFetchMany(l => l.LayoutOptions) .FetchMany(p => p.MasterPages) .AsQueryable(); if (cmsConfiguration.Security.AccessControlEnabled) { pageQuery = pageQuery.FetchMany(f => f.AccessRules); } var page = pageQuery.ToList().FirstOne(); var beforeChange = new UpdatingPagePropertiesModel(page); var roles = page.IsMasterPage ? new[] { RootModuleConstants.UserRoles.EditContent, RootModuleConstants.UserRoles.PublishContent, RootModuleConstants.UserRoles.Administration } : new[] { RootModuleConstants.UserRoles.EditContent, RootModuleConstants.UserRoles.PublishContent }; if (cmsConfiguration.Security.AccessControlEnabled) { AccessControlService.DemandAccess(page, Context.Principal, AccessLevel.ReadWrite, roles); } else { AccessControlService.DemandAccess(Context.Principal, roles); } var canEdit = page.IsMasterPage ? SecurityService.IsAuthorized( Context.Principal, RootModuleConstants.UserRoles.MultipleRoles(RootModuleConstants.UserRoles.EditContent, RootModuleConstants.UserRoles.Administration)) : SecurityService.IsAuthorized(Context.Principal, RootModuleConstants.UserRoles.EditContent); IList <PageProperties> translations = null; if (canEdit && isMultilanguageEnabled && !page.IsMasterPage) { translations = LoadAndValidateTranslations(page, request); } // 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; masterPageService.PrepareForUpdateChildrenMasterPages(page, request.MasterPageId, out newMasterIds, out oldMasterIds, out childrenPageIds, out existingChildrenMasterPages); IList <SitemapNode> updatedNodes = null; // Start transaction, only when everything is already loaded UnitOfWork.BeginTransaction(); Models.Redirect redirectCreated = null; var initialSeoStatus = page.HasSEO; request.PageUrl = urlService.FixUrl(request.PageUrl); if (canEdit && !string.Equals(page.PageUrl, request.PageUrl)) { pageService.ValidatePageUrl(request.PageUrl, request.Id); if (request.RedirectFromOldUrl) { var redirect = redirectService.CreateRedirectEntity(page.PageUrl, request.PageUrl); if (redirect != null) { Repository.Save(redirect); redirectCreated = redirect; } } if (request.UpdateSitemap) { updatedNodes = sitemapService.ChangeUrlsInAllSitemapsNodes(page.PageUrl, request.PageUrl); } page.PageUrl = request.PageUrl; } List <PageProperties> updatePageTranslations = null; if (canEdit) { page.PageUrlHash = page.PageUrl.UrlHash(); page.ForceAccessProtocol = request.ForceAccessProtocol; categoryService.CombineEntityCategories <PageProperties, PageCategory>(page, request.Categories); page.Title = request.PageName; page.CustomCss = request.PageCSS; page.CustomJS = request.PageJavascript; masterPageService.SetMasterOrLayout(page, request.MasterPageId, request.TemplateId); if (isMultilanguageEnabled && !page.IsMasterPage) { updatePageTranslations = UpdatePageTranslations(page, translations, request); } } var publishDraftContent = false; if (request.CanPublishPage && !page.IsMasterPage) { AccessControlService.DemandAccess(Context.Principal, RootModuleConstants.UserRoles.PublishContent); if (request.IsPagePublished) { if (page.Status != PageStatus.Published) { page.Status = PageStatus.Published; page.PublishedOn = DateTime.Now; publishDraftContent = true; } } else { page.Status = PageStatus.Unpublished; } } IList <PageOption> pageOptions = page.Options.Distinct().ToList(); if (canEdit) { if (!page.IsMasterPage) { page.UseNoFollow = request.UseNoFollow; page.UseNoIndex = request.UseNoIndex; page.IsArchived = request.IsArchived; } page.UseCanonicalUrl = request.UseCanonicalUrl; page.Version = request.Version; page.Image = request.Image != null && request.Image.ImageId.HasValue ? Repository.AsProxy <MediaImage>(request.Image.ImageId.Value) : null; page.SecondaryImage = request.SecondaryImage != null && request.SecondaryImage.ImageId.HasValue ? Repository.AsProxy <MediaImage>(request.SecondaryImage.ImageId.Value) : null; page.FeaturedImage = request.FeaturedImage != null && request.FeaturedImage.ImageId.HasValue ? Repository.AsProxy <MediaImage>(request.FeaturedImage.ImageId.Value) : null; pageOptions = optionService.SaveOptionValues(request.OptionValues, pageOptions, () => new PageOption { Page = page }); if (cmsConfiguration.Security.AccessControlEnabled) { page.AccessRules.RemoveDuplicateEntities(); var accessRules = request.UserAccessList != null?request.UserAccessList.Cast <IAccessRule>().ToList() : null; accessControlService.UpdateAccessControl(page, accessRules); } } // Notify about page properties changing. var cancelEventArgs = Events.PageEvents.Instance.OnPagePropertiesChanging(beforeChange, new UpdatingPagePropertiesModel(page)); if (cancelEventArgs.Cancel) { Context.Messages.AddError(cancelEventArgs.CancellationErrorMessages.ToArray()); return(null); } Repository.Save(page); IList <Tag> newTags = null; if (canEdit) { masterPageService.UpdateChildrenMasterPages(existingChildrenMasterPages, oldMasterIds, newMasterIds, childrenPageIds); tagService.SavePageTags(page, request.Tags, out newTags); } if (publishDraftContent) { contentService.PublishDraftContent(page.Id); } UnitOfWork.Commit(); // Notify about page properties change. page.Options = pageOptions; Events.PageEvents.Instance.OnPagePropertiesChanged(page); // Notify about translation properties changed if (updatePageTranslations != null) { updatePageTranslations.ForEach(Events.PageEvents.Instance.OnPagePropertiesChanged); } // Notify about redirect creation. if (redirectCreated != null) { Events.PageEvents.Instance.OnRedirectCreated(redirectCreated); } // Notify about SEO status change. if (initialSeoStatus != page.HasSEO) { Events.PageEvents.Instance.OnPageSeoStatusChanged(page); } // Notify about new tags. Events.RootEvents.Instance.OnTagCreated(newTags); // Notify about updated sitemap nodes. if (updatedNodes != null) { var updatedSitemaps = new List <Models.Sitemap>(); foreach (var node in updatedNodes) { Events.SitemapEvents.Instance.OnSitemapNodeUpdated(node); if (!updatedSitemaps.Contains(node.Sitemap)) { updatedSitemaps.Add(node.Sitemap); } } foreach (var updatedSitemap in updatedSitemaps) { Events.SitemapEvents.Instance.OnSitemapUpdated(updatedSitemap); } } return(new SavePageResponse(page)); }
/// <summary> /// Puts the specified request. /// </summary> /// <param name="request">The request.</param> /// <returns><c>PutPageResponse</c> with created or updated item id.</returns> public PutPagePropertiesResponse Put(PutPagePropertiesRequest request) { if (request.Data.IsMasterPage) { accessControlService.DemandAccess(securityService.GetCurrentPrincipal(), RootModuleConstants.UserRoles.Administration); } else { accessControlService.DemandAccess(securityService.GetCurrentPrincipal(), RootModuleConstants.UserRoles.EditContent); } PageProperties pageProperties = null; var isNew = !request.Id.HasValue || request.Id.Value.HasDefaultValue(); if (!isNew) { pageProperties = repository.AsQueryable <PageProperties>(e => e.Id == request.Id.GetValueOrDefault()) .FetchMany(p => p.Options) .Fetch(p => p.Layout) .ThenFetchMany(l => l.LayoutOptions) .FetchMany(p => p.MasterPages) .FetchMany(f => f.AccessRules) .ToList() .FirstOrDefault(); isNew = pageProperties == null; } UpdatingPagePropertiesModel beforeChange = null; if (isNew) { pageProperties = new PageProperties { Id = request.Id.GetValueOrDefault(), Status = PageStatus.Unpublished, AccessRules = new List <AccessRule>() }; } else if (request.Data.Version > 0) { pageProperties.Version = request.Data.Version; } if (!isNew) { beforeChange = new UpdatingPagePropertiesModel(pageProperties); } if (!isNew && pageProperties.IsMasterPage != request.Data.IsMasterPage) { const string message = "IsMasterPage cannot be changed for updating page. It can be modified only when creating a page."; var logMessage = string.Format("{0} PageId: {1}", message, request.Id); throw new ValidationException(() => message, logMessage); } // 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; masterPageService.PrepareForUpdateChildrenMasterPages(pageProperties, request.Data.MasterPageId, out newMasterIds, out oldMasterIds, out childrenPageIds, out existingChildrenMasterPages); unitOfWork.BeginTransaction(); if (!string.IsNullOrEmpty(request.Data.PageUrl) || string.IsNullOrEmpty(pageProperties.PageUrl)) { var pageUrl = request.Data.PageUrl; if (string.IsNullOrEmpty(pageUrl) && !string.IsNullOrWhiteSpace(request.Data.Title)) { pageUrl = pageService.CreatePagePermalink(request.Data.Title, null, null, request.Data.LanguageId, request.Data.CategoryId); } else { pageUrl = urlService.FixUrl(pageUrl); pageService.ValidatePageUrl(pageUrl, request.Id); } pageProperties.PageUrl = pageUrl; pageProperties.PageUrlHash = pageUrl.UrlHash(); } pageProperties.Title = request.Data.Title; pageProperties.Description = request.Data.Description; var newStatus = request.Data.IsMasterPage || request.Data.IsPublished ? PageStatus.Published : PageStatus.Unpublished; if (!request.Data.IsMasterPage && pageProperties.Status != newStatus) { accessControlService.DemandAccess(securityService.GetCurrentPrincipal(), RootModuleConstants.UserRoles.PublishContent); } pageProperties.Status = newStatus; pageProperties.PublishedOn = request.Data.IsPublished && !request.Data.PublishedOn.HasValue ? DateTime.Now : request.Data.PublishedOn; masterPageService.SetMasterOrLayout(pageProperties, request.Data.MasterPageId, request.Data.LayoutId); pageProperties.Category = request.Data.CategoryId.HasValue ? repository.AsProxy <Category>(request.Data.CategoryId.Value) : null; pageProperties.IsArchived = request.Data.IsArchived; pageProperties.IsMasterPage = request.Data.IsMasterPage; pageProperties.LanguageGroupIdentifier = request.Data.LanguageGroupIdentifier; pageProperties.Language = request.Data.LanguageId.HasValue && !request.Data.LanguageId.Value.HasDefaultValue() ? repository.AsProxy <Language>(request.Data.LanguageId.Value) : null; pageProperties.Image = request.Data.MainImageId.HasValue ? repository.AsProxy <MediaImage>(request.Data.MainImageId.Value) : null; pageProperties.FeaturedImage = request.Data.FeaturedImageId.HasValue ? repository.AsProxy <MediaImage>(request.Data.FeaturedImageId.Value) : null; pageProperties.SecondaryImage = request.Data.SecondaryImageId.HasValue ? repository.AsProxy <MediaImage>(request.Data.SecondaryImageId.Value) : null; pageProperties.CustomCss = request.Data.CustomCss; pageProperties.CustomJS = request.Data.CustomJavaScript; pageProperties.UseCanonicalUrl = request.Data.UseCanonicalUrl; pageProperties.UseNoFollow = request.Data.UseNoFollow; pageProperties.UseNoIndex = request.Data.UseNoIndex; if (request.Data.MetaData != null) { pageProperties.MetaTitle = request.Data.MetaData.MetaTitle; pageProperties.MetaDescription = request.Data.MetaData.MetaDescription; pageProperties.MetaKeywords = request.Data.MetaData.MetaKeywords; } IList <Tag> newTags = null; if (request.Data.Tags != null) { tagService.SavePageTags(pageProperties, request.Data.Tags, out newTags); } if (request.Data.AccessRules != null) { pageProperties.AccessRules.RemoveDuplicateEntities(); var accessRules = request.Data.AccessRules.Select( r => (IAccessRule) new AccessRule { AccessLevel = (Core.Security.AccessLevel)(int) r.AccessLevel, Identity = r.Identity, IsForRole = r.IsForRole }) .ToList(); accessControlService.UpdateAccessControl(pageProperties, accessRules); } if (request.Data.PageOptions != null) { var options = request.Data.PageOptions.ToServiceModel(); var pageOptions = pageProperties.Options != null?pageProperties.Options.Distinct() : null; pageProperties.Options = optionService.SaveOptionValues(options, pageOptions, () => new PageOption { Page = pageProperties }); } if (!isNew) { // Notify about page properties changing. var cancelEventArgs = Events.PageEvents.Instance.OnPagePropertiesChanging(beforeChange, new UpdatingPagePropertiesModel(pageProperties)); if (cancelEventArgs.Cancel) { throw new CmsApiValidationException( cancelEventArgs.CancellationErrorMessages != null && cancelEventArgs.CancellationErrorMessages.Count > 0 ? string.Join(",", cancelEventArgs.CancellationErrorMessages) : "Page properties saving was canceled."); } } repository.Save(pageProperties); // // If creating new page, page id is unknown when children pages are loaded, so Guid may be empty // Updating id to saved page's Id manually // if (isNew && childrenPageIds != null && childrenPageIds.Count == 1 && childrenPageIds[0].HasDefaultValue()) { childrenPageIds[0] = pageProperties.Id; } masterPageService.UpdateChildrenMasterPages(existingChildrenMasterPages, oldMasterIds, newMasterIds, childrenPageIds); unitOfWork.Commit(); // Fire events. Events.RootEvents.Instance.OnTagCreated(newTags); if (isNew) { Events.PageEvents.Instance.OnPageCreated(pageProperties); } else { Events.PageEvents.Instance.OnPagePropertiesChanged(pageProperties); } return(new PutPagePropertiesResponse { Data = pageProperties.Id }); }
/// <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); }